Public
Edited
Jun 26, 2023
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
helloWorld = html`<p id='select-by-id'>Hello World!</p>`
Insert cell
Insert cell
myElement = d3.select('#select-by-id') // Refer to an ID of an element with a "#" at the beginning
Insert cell
Insert cell
myElement.text()
Insert cell
Insert cell
// myElement.text('Hello D3!') // 🛠️ Uncomment and run cell (Shift + Enter or click play button).
Insert cell
Insert cell
html`<ul class='select-by-class'>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>`
Insert cell
Insert cell
// 🛠️ Uncomment the following lines (select lines, then Ctrl + Shift + /) and run cell (Shift + Enter or click play button).

// d3.select('.select-by-class') // Select the list element using the class attribute.
// .selectAll('li') // Select all list item elements.
// .text('Hello list!') // Change the text of each list item.
// .style('color', 'red') // Change the text color of each list item.
Insert cell
Insert cell
listOfDataValues = [10, 20, 30, 40, 50]
Insert cell
Insert cell
html`<ul id='data-list'><li>No connected data yet...</li></ul>`
Insert cell
Insert cell
// 🛠️ Uncomment the following lines (select lines, then Ctrl + Shift + /) and run cell (Shift + Enter or click blue play button).

// d3.select('#data-list') // Select the list element by its ID.
// .selectAll('li') // Select all list item elements that are children of the list element.
// .data(listOfDataValues) // Connect the data to the selection.
// .join('li') // For each data value, create or update the associated list item element.
// .text((d, i) => "The data at index #" + i + " is " + d) // Change the text of the list item based on the data value.
// .style("color", d => d > 30 ? "purple" : "black") // Change the text color of the list item based on the data value.
Insert cell
Insert cell
Insert cell
exampleDocument = html`
<!DOCTYPE html>
<html>
<body>
<svg width=200 height=200 style='border: 1px solid black'>
<circle cx=100 cy=100 r=50 fill='steelblue' stroke='black'/>
</svg>
</body>
</html>
`
Insert cell
Insert cell
html`<div id='container'>No SVG image added yet...</div>`
Insert cell
// 🛠️ Uncomment the following lines (select lines, then Ctrl + Shift + /) and run cell (Shift + Enter or click blue play button).

// {
// // Remove text and any previously added SVGs from container.
// d3.select("#container")
// .text('')
// .selectAll('svg')
// .remove();

// // Create the SVG image.
// const svg = d3.select("#container") // Select the HTML element that will contain the SVG image.
// .append("svg") // Append / create a new SVG image.
// .attr("width", 200) // Set the width of the image.
// .attr("height", 200) // Set the height of the image.
// .style("border", "1px solid black"); // Give the image an outline.

// svg.append("circle") // Add a circle to the SVG image.
// .attr("id", "mycircle") // Give the circle an ID so we can refer to it later.
// .attr("cx", 100) // Set the x center of the circle.
// .attr("cy", 100) // Set the y center of the circle.
// .attr("r", 50) // Set the radius of the circle.
// .style("fill", "purple") // Set the fill color of the circle.
// .style('stroke', 'black'); // The the outline color of the circle.

// return svg; // Return the svg image.
// }
Insert cell
Insert cell
// circle = d3.select("#mycircle") // 🛠️ Get a reference of the circle in the SVG image.color
Insert cell
// circle.attr("r", 85); // 🛠️ Change the radius of the circle.
Insert cell
// circle.style("fill", "green"); // 🛠️ Change the fill color of the cirle.
Insert cell
Insert cell
barChartData = [50, 90, 120, 250, 200, 150, 100, 50, 10, 40, 80, 60, 40, 70, 50, 90, 120, 60, 40, 70, 50, 60, 40, 70]
Insert cell
Insert cell
chartWidth = width // The default value of "width" is the width of a notebook cell (if not overwritten elsewhere in the notebook)
Insert cell
chartHeight = 250;
Insert cell
Insert cell
{
// Calculate the sizing of the rectangles.
const barPadding = 5; // Space between rectangles.
const barWidth = chartWidth / barChartData.length; // Width of one bar / rectangle.
const maxValue = Math.max(...barChartData); // Maximum data value.

// Create the SVG image with the chosen width and height.
const svg = d3.create('svg')
.attr('width', chartWidth)
.attr('height', chartHeight);

// Create the bars / rectangles in the image.
svg.selectAll('rect') // Select all existing rectangles (even if there are none at first).
.data(barChartData) // Connect the data to the selection.
.join('rect') // Create a mapping between the data values and rectangles.
.attr('x', (d, i) => barWidth * i) // Set the horizontal position of the rectangle.
.attr('y', d => chartHeight - chartHeight * d / maxValue) // Set the vertical position of the rectangle.
.attr('width', barWidth - barPadding) // Set the rectangle width.
.attr('height', d => chartHeight * d / maxValue) // Set the rectangle height.
.style('fill', 'pink') // Set the rectangle fill color.
.style('stroke', 'black'); // Set the rectangle outline color.

// Return the HTML element of the SVG object.
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
margin = ({ left: 30, top: 10, right: 10, bottom: 30 }) // Defines the area of the chart in the image.
Insert cell
Insert cell
yScale = d3.scaleLinear() // Get a linear scale object.
.domain([0, d3.max(barChartData)]) // Set input min / max (0 to max data value).
.range([chartHeight - margin.bottom, margin.top]) // Set output min / max (top and bottom edge of chart area).
Insert cell
Insert cell
dataValueInImageCoordinates = yScale(25) // Returns y position in pixels given a data value.
Insert cell
Insert cell
xScale = d3.scaleBand() // Get a scale object for discrete bands.
.domain(d3.range(barChartData.length)) // Set input min / max (0 to max data array index).
.range([margin.left, chartWidth - margin.right]) // Set output min / max (left to right edge of chart area).
.padding(0.1); // Set padding between bands.
Insert cell
Insert cell
dataIndexInImageCoordinates = xScale(5) // Returns x position in pixels given an index of the data array.
Insert cell
Insert cell
Insert cell
{
// Create the SVG image to draw the axis.
const width = 60; // Fixed width for vertical axis.
const svg = d3.select(DOM.svg(width, chartHeight)); // <- Shortcut for creating SVGs in Observable.

// 🧪 Experiment with other scales to see how the axis changes in the image. 🧪
// For example, try to set a different input domain.
// const yScale = d3.scaleLinear() // Get a linear scale object.
// .domain([0, 10]) // ⬅️ Set input min / max.
// .range([chartHeight - margin.bottom, margin.top]) // Set output min / max.
// Create the SVG elements for the axis.
svg.append('g') // Create a SVG group.
.call(d3.axisLeft(yScale)) // Call the D3 axis method.
.attr('transform', `translate(${margin.left}, 0)`); // Move the axis to the chart area.

// Return the HTML element of the SVG object.
return svg.node();
}
Insert cell
Insert cell
Insert cell
{
// Create the SVG image to draw the axis.
const height = 60; // Fixed height for horizontal axis.
const svg = d3.select(DOM.svg(chartWidth, height)); // <- Shortcut for creating SVGs in Observable.

// 🧪 Experiment with other scales to see how the axis changes in the image. 🧪
// For example, try to set a different input domain or padding.
// const xScale = d3.scaleBand() // Get a scale object for discrete bands.
// .domain(['One', 'Two', 'Three', 'Four', 'Five']) // ⬅️ Set input min / max.
// .range([margin.left, chartWidth - margin.right]) // Set output min / max (left to right edge of chart area).
// .padding(0.5); // Set padding between bands.
// Create the SVG elements for the axis.
svg.append('g') // Create a SVG group.
.call(d3.axisBottom(xScale)) // Call the D3 axis method for the group.
.attr('transform', `translate(0, ${height - margin.bottom})`); // Move the axis to the chart area.

// Return the HTML element of the SVG object.
return svg.node();
}
Insert cell
Insert cell
Insert cell
function createBarChart(width, height, margin, xScale, yScale, data) {
// Create the SVG.
const svg = d3.select(DOM.svg(width, height));
// Create and append the Y axis.
svg.append('g')
.attr('class', 'y-axis') // Give the axis a class name, so we can refer to it later (see excercise E2)
.attr('transform', `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));

// Create and append the X axis.
svg.append('g')
.attr('class', 'x-axis') // Give the axis a class name, so we can refer to it later (see excercise E2)
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale));
// Create and join the bars / rectangles.
svg.selectAll('rect')
.data(data)
.join('rect')
.attr('x', (_, i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.style('stroke', 'black')
.attr('fill', 'pink');

return svg.node();
}
Insert cell
Insert cell
barChart = createBarChart(chartWidth, chartHeight, margin, xScale, yScale, barChartData)
Insert cell
Insert cell
Insert cell
Insert cell
// Returns a color given a normalized data value.
function getColorScale(schemeName) {
// Return D3's predefined color interpolator by name.
return d3[`interpolate${schemeName}`]
}
Insert cell
// An array of available color scheme names.
colorSchemeNames = ['Blues', 'Greens', 'Greys', 'Oranges', 'Purples', 'Reds', 'Inferno', 'Magma', 'Plasma', 'Warm', 'Cool', 'CubehelixDefault', 'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBuGn', 'PuBu', 'PuRd', 'RdPu', 'YlGnBu', 'YlGn', 'YlOrBr', 'YlOrRd']
Insert cell
// Create a selection box to choose a color scheme by name.
viewof selectedColorSchemeName = Inputs.select(colorSchemeNames, {value: "RdPu", label: "Color scheme"})
Insert cell
// Create the color scale function.
colorScale = getColorScale(selectedColorSchemeName)
Insert cell
// Use the color scale function to turn normalized values into colors.
normalizedValueInColor = colorScale(0.5)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
normalizeData = d3.scaleLinear() // Get a linear scale object.
.domain(d3.extent(barChartData)) // Set input min / max (min and max data value).
.range([0, 1]); // Set output min / max (0 and 1).
Insert cell
Insert cell
normalizedDataValue = normalizeData(barChartData[5]) // Returns normalized value between 0 and 1 given a data value.
Insert cell
Insert cell
dataValueInColor = colorScale(normalizeData(barChartData[5])) // Returns a color given a data value.
Insert cell
Insert cell
barChartColored = createBarChart(chartWidth, chartHeight, margin, xScale, yScale, barChartData)
Insert cell
Insert cell
d3.select(barChartColored) // Select the chart to color.
.selectAll('rect') // Select all rectangles in the chart.
.style('fill', d => colorScale(normalizeData(d))) // Set the fill color using the assigned data value as input to the color function.
Insert cell
Insert cell
yAxisLabel = '↑ Measured value'
Insert cell
xAxisLabel = 'Observation'
Insert cell
Insert cell
d3.create("text") // <- ⚠️ Use append instead of create when attaching the element to a selected axis.
.attr("x", chartWidth) // Set x position of label (e.g., at the right edge of the chart).
.attr("y", marginWithLabels.bottom - 4) // Set y position of label (e.g., at the bootom edge of the chart).
.attr("fill", "currentColor") // Set text color.
.attr("text-anchor", "end") // Set text anchor.
.text("Example label for X-axis") // Set label text.
.node() // <- ⚠️ Only for preview, remove when attaching to the axis!
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
marginWithLabels = ({ left: 30, top: 20, right: 10, bottom: 32 }); // Higher values for top and bottom.
Insert cell
Insert cell
yScaleWithLabels = d3.scaleLinear() // Get a linear scale object.
.domain([0, d3.max(barChartData)]) // Set input min / max (0 to max data value).
.range([chartHeight - marginWithLabels.bottom, marginWithLabels.top]) // Set output min / max (top and bottom edge of chart area).
Insert cell
Insert cell
barChartLabeled = createBarChart(chartWidth, chartHeight, marginWithLabels, xScale, yScaleWithLabels, barChartData)
Insert cell
Insert cell
d3.select(barChartLabeled) // Select the chart.
.select('.y-axis') // Select the Y-axis by class name.
.append("text") // Append text element.
.attr("x", -marginWithLabels.left) // Set x position.
.attr("y", 10) // Set y position.
.attr("fill", "currentColor") // Set the text color.
.attr("text-anchor", "start") // Set the text anchor.
.text(yAxisLabel) // Set the text label.
Insert cell
Insert cell
d3.select(barChartLabeled) // Select the chart.
.select('.x-axis') // Select the X-axis by class name.
.append("text") // Append text element.
.attr("x", chartWidth) // Set x position.
.attr("y", marginWithLabels.bottom - 4) // Set y position.
.attr("fill", "currentColor") // Set the text color.
.attr("text-anchor", "end") // Set the text anchor.
.text(xAxisLabel) // Set the text label.
Insert cell
Insert cell
d3.select(barChartLabeled) // Select the chart.
.select('.x-axis') // Select the X-axis by class name.
.call(d3.axisBottom(xScale).tickFormat((_, i) => dataIndexLabels[i])); // Change the tick format.
Insert cell
Insert cell
d3.select(barChartLabeled) // Select the chart.
.select('.y-axis') // Select the Y-axis.
.selectAll('line.y-grid') // Select all grid lines.
.data(yScaleWithLabels.ticks()) // Add the tick values of Y-axis as data.
.join('line') // Map the tick values to line elements.
.attr('class', 'y-grid') // Give each line a class name.
.attr('x1', 0) // Set x start position.
.attr('x2', chartWidth - marginWithLabels.left - marginWithLabels.right) // Set x end position.
.attr('y1', d => yScaleWithLabels(d)) // Set y start position.
.attr('y2', d => yScaleWithLabels(d)) // Set y end position.
.style('stroke', 'lightgray'); // Set the line color.
Insert cell
Insert cell
import { alphabet as newDataset } from "@observablehq/plot-test-data"
Insert cell
newDataset
Insert cell
Insert cell
Insert cell
newData = newDataset.data.map((d) => d.frequency)
Insert cell
newDataLabels = newDataset.data.map((d) => d.letter)
Insert cell
Insert cell
function createBarChartLabeled(width, height, margin, data, dataLabels, xAxisLabel, yAxisLabel) {
// Create the SVG.
const svg = d3.select(DOM.svg(width, height));

// Create the X scale.
const xScale = d3.scaleBand()
.domain(d3.range(data.length))
.range([margin.left, width - margin.right])
.padding(0.1);

// Create the Y scale.
const yScale = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([height - margin.bottom, margin.top]);

// Create and append the Y axis.
const yAxis = svg.append('g')
.attr('class', 'y-axis')
.attr('transform', `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));

// Add the Y-axis label.
if (yAxisLabel) {
yAxis.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yAxisLabel);
}

// Add the Y-axis grid lines.
yAxis.selectAll('line.y-grid')
.data(yScale.ticks())
.join('line')
.attr('class', 'y-grid')
.attr('x1', 0)
.attr('x2', width - margin.left - margin.right)
.attr('y1', d => yScale(d))
.attr('y2', d => yScale(d))
.style('stroke', 'lightgray');
// Create and append the X axis.
const xAxis = svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${height - margin.bottom})`)
.call(dataLabels ? d3.axisBottom(xScale).tickFormat((_, i) => dataLabels[i]) : d3.axisBottom(xScale));

// Add the X-axis label.
if (xAxisLabel) {
xAxis.append("text")
.attr("x", width)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xAxisLabel);
}
// Create and join the bars / rectangles.
svg.selectAll('rect')
.data(data)
.join('rect')
.attr('x', (_, i) => xScale(i))
.attr('y', d => yScale(d))
.attr('width', xScale.bandwidth())
.attr('height', d => yScale(0) - yScale(d))
.style('stroke', 'black')
.attr('fill', 'pink');

return svg.node();
}
Insert cell
Insert cell
createBarChartLabeled(chartWidth, chartHeight, marginWithLabels, newData, newDataLabels, xAxisLabel, yAxisLabel)
Insert cell
Insert cell
replay = Inputs.button("Animate circle!", { reduce: () => {
d3.select(animatedSvg) // Select the SVG.
.select('circle') // Select the circle.
.attr('cx', 20) // Reset the position.
.transition() // Add a transition.
.duration(2000) // Set the transition duration in milliseconds.
.attr('cx', width - 20); // Set the final value when the transition ends.
}})
Insert cell
animatedSvg = {
const svg = d3.select(DOM.svg(width, 50)) // Create an SVG.
.style("border", "1px solid black"); // Add a border.

const circle = svg.append('circle') // Append a circle.
.attr('cx', 20) // Set the x position.
.attr('cy', 25) // Set the y position.
.attr('r', 15) // Set the radius.
.style('fill', '#CBC3E3') // Set the fill color.
.style('stroke', 'black'); // Set the outline color.

yield svg.node(); // Return the HTML element.
}
Insert cell
Insert cell
function createRandomData(data) {
// Create a new data array with the same length and maximum value as the original data.
return Array.from({length: data.length}, () => Math.random() * d3.max(data));
}
Insert cell
Insert cell
function updateChart(chart, yScale, data) {
d3.select(chart) // Select the chart.
.selectAll('rect') // Select all rectangles in the SVG image by their class name.
.data(data) // Connect the data to the selection.
.join('rect') // Only necessary if number of data values changes.
.transition() // Add a transition.
.duration(2000) // Set the transition duration (2000 milliseconds).
.attr('y', d => yScale(d)) // Update the y position.
.attr('height', d => yScale(0) - yScale(d)) // Update the height.
.attr('fill', d => colorScale(normalizeData(d))); // Update the fill color.
}
Insert cell
Insert cell
barChartUpdated = createBarChartLabeled(chartWidth, chartHeight, margin, barChartData)
Insert cell
Insert cell
updateButton = Inputs.button("Update data and chart", { reduce: () => {
// Create new data values.
const newData = createRandomData(barChartData);
// Update the chart.
updateChart(barChartUpdated, yScale, newData);
}})
Insert cell
Insert cell
barChartAnimated = createBarChartLabeled(chartWidth, chartHeight, margin, barChartData)
Insert cell
Insert cell
{
// Wait for the cell to be visible before starting the animation.
await visibility();
// Animation loop
while (true) {
// Create new data values.
const newData = createRandomData(barChartData);
// Update the chart.
updateChart(barChartAnimated, yScale, newData);
// Wait some time before the next animation.
await Promises.delay(4000);
}
}
Insert cell
Insert cell
temperatureAnomalyDataset = FileAttachment("HadCRUT5_global_annual.csv").csv({typed: true})
Insert cell
Insert cell
temperatureAnomalyValues = temperatureAnomalyDataset.map(d => d['Anomaly (deg C)'])
Insert cell
Insert cell
temperatureAnomalyRange = d3.extent(temperatureAnomalyValues)
Insert cell
...and then create the scale function.
Insert cell
temperatureAnomalyColorScale = d3.scaleDiverging(t => d3.interpolateRdBu(1 - t)) // Invert interpolation value to get correct colors.
.domain([temperatureAnomalyRange[0], 0, temperatureAnomalyRange[1]]) // Set zero a mid point of color scale.
Insert cell
Insert cell
Insert cell
{
const height = 200; // Set the height for the stripes.
const svg = d3.select(DOM.svg(width, height)) // Create the SVG element.
.style('border', '1px solid black'); // Optionally, give the SVG image an outline.

// Create a band scale for positioning and sizing the rectangles.
const xScale = d3.scaleBand()
.domain(d3.range(temperatureAnomalyValues.length)) // Set input from 0 to max data array index.
.range([0, width]) // Set output from left to right edge of chart area.
.padding(0.0); // Set padding between bands.
// Create the colored rectangles.
svg.selectAll('rect') // Select all rectangles.
.data(temperatureAnomalyValues) // Attach data to the selection.
.join('rect') // Establish a mapping between data values and the rectanlges in the selection.
.attr('x', (_, i) => xScale(i)) // Horizontally offset each rectangle.
.attr('y', 0) // Set the y position.
.attr('width', xScale.bandwidth) // Set the rectangle width.
.attr('height', height) // Set the rectangle height.
.style('fill', d => temperatureAnomalyColorScale(d)) // Calculate the color based on the data value.

// Return the HTML element for the SVG.
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
manipulatedData = ({ value: width / 5 }) // Initialize the data object (also sets the intial circle position).
Insert cell
Insert cell
Insert cell
Insert cell
colorInterpolators = colorSchemeNames.map(p => ({ name: p, interpolator: d3[`interpolate${p}`]}))
Insert cell
import {Legend} from "@d3/color-legend"
Insert cell
Insert cell
function duplicateSVG(originalNode, width, height) {
const original = d3.select(originalNode);
const other = original.clone(true);
const copy = d3.select(DOM.svg(original.attr('width'), original.attr('height')));
copy.append(() => other.node());
return copy.node();
}
Insert cell
Insert cell
function* drawGradient(colors, n = 256) {
// Create a new canvas with it's width set to the number of colors and it's height to one.
const ctx = DOM.context2d(n, 1, 1);
// Adjust the styling of the canvas to it is displayed larger.
ctx.canvas.style.width = "100%";
ctx.canvas.style.height = "40px";
ctx.canvas.style.imageRendering = "pixelated";
// Fill the canvas's pixels based on the given color function.
for (let i = 0; i < n; ++i) {
// For each color draw a colored rectangle in the size of a pixel.
ctx.fillStyle = colors(i / (n - 1));
ctx.fillRect(i, 0, 1, 1);
}

// Return the canvas.
yield ctx.canvas;
}
Insert cell
Insert cell
// Thanks to @gooostaw (https://stackoverflow.com/a/64456745)
function numberToLetters(num) {
let letters = '';
while (num >= 0) {
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
num = Math.floor(num / 26) - 1;
}
return letters;
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more