Notebooks 2.0 is here.

Public
Edited
Sep 22, 2022
Insert cell
# ex11: D3 in Observable

## 😜 Exercise 1
Implement the Browser vs. Observable example chart
Insert cell
<svg id="svg0" style="background-color: #eee"></svg>

<script src="d3.min.js"></script>
Insert cell
data1 = [{x: 20, y: 20, r: 10},{x: 50, y: 50, r: 30},{x: 80, y: 80, r: 10}];

Insert cell
width1 = 100;
Insert cell
height1 = 100;
Insert cell
svg = d3.select('#svg0')
.attr('width', width1)
.attr('height', height1);
Insert cell
svg.append('g')
.attr('fill', 'steelblue')
.selectAll('rect')
.data(data1)
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', d => d.r);
Insert cell
chart1 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append('g')
.attr('fill', 'steelblue')
.selectAll('rect')
.data(data)
.join('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', d => d.r);
return svg.node();
}

Insert cell
//SEPARATE CELL
data = [{x: 20, y: 20, r: 10},
{x: 50, y: 50, r: 30},
{x: 80, y: 80, r: 10}];

Insert cell
//SEPARATE CELL
height = 100;

Insert cell
//SEPARATE CELL
width = 100;

Insert cell
//SEPARATE CELL
d3 = require("d3@7")
Insert cell
Insert cell
chart = BarChart(alphabet, {
x: d => d.letter,
y: d => d.frequency,
xDomain: d3.groupSort(alphabet, ([d]) => -d.frequency, d => d.letter), // sort by descending frequency
yFormat: "%",
yLabel: "↑ Frequency",
width:1000,
height: 500,
color: "steelblue"
})
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/bar-chart
function BarChart(data, {
x = (d, i) => i, // given d in data, returns the (ordinal) x-value
y = d => d, // given d in data, returns the (quantitative) y-value
title, // given d in data, returns the title text
marginTop = 20, // the top margin, in pixels
marginRight = 0, // the right margin, in pixels
marginBottom = 30, // the bottom margin, in pixels
marginLeft = 40, // the left margin, in pixels
width = 640, // the outer width of the chart, in pixels
height = 400, // the outer height of the chart, in pixels
xDomain, // an array of (ordinal) x-values
xRange = [marginLeft, width - marginRight], // [left, right]
yType = d3.scaleLinear, // y-scale type
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
xPadding = 0.1, // amount of x-range to reserve to separate bars
yFormat, // a format specifier string for the y-axis
yLabel, // a label for the y-axis
color = "currentColor" // bar fill color
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);

// Compute default domains, and unique the x-domain.
if (xDomain === undefined) xDomain = X;
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
xDomain = new d3.InternSet(xDomain);

// Omit any data not present in the x-domain.
const I = d3.range(X.length).filter(i => xDomain.has(X[i]));

// Construct scales, axes, and formats.
const xScale = d3.scaleBand(xDomain, xRange).padding(xPadding);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);

// Compute titles.
if (title === undefined) {
const formatValue = yScale.tickFormat(100, yFormat);
title = i => `${X[i]}\n${formatValue(Y[i])}`;
} else {
const O = d3.map(data, d => d);
const T = title;
title = i => T(O[i], i, data);
}

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));

const bar = svg.append("g")
.attr("fill", color)
.selectAll("rect")
.data(I)
.join("rect")
.attr("x", i => xScale(X[i]))
.attr("y", i => yScale(Y[i]))
.attr("height", i => yScale(0) - yScale(Y[i]))
.attr("width", xScale.bandwidth());

if (title) bar.append("title")
.text(title);

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);

return svg.node();
}
Insert cell
import {howto, altplot} from "@d3/example-components"
Insert cell
Insert cell
chart4 = BoxPlot(diamonds, {
x: d => d.carat,
y: d => d.price,
xLabel: "Carats →",
yLabel: "↑ Price ($)",
width:800,
height: 500
})
Insert cell
diamonds = FileAttachment("diamonds.csv").csv({typed: true})
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/box-plot
function BoxPlot(data, {
x = ([x]) => x, // given d in data, returns the (quantitative) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
marginTop = 20, // top margin, in pixels
marginRight = 30, // right margin, in pixels
marginBottom = 30, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
inset = 0.5, // left and right inset
insetLeft = inset, // inset for left edge of box, in pixels
insetRight = inset, // inset for right edge of box, in pixels
xType = d3.scaleLinear, // type of x-scale
xDomain, // [xmin, xmax]
xRange = [marginLeft, width - marginRight], // [left, right]
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
thresholds = width / 40, // approximative number of thresholds
stroke = "currentColor", // stroke color of whiskers, median, outliers
fill = "#ddd", // fill color of boxes
jitter = 4, // amount of random jitter for outlier dots, in pixels
xFormat, // a format specifier string for the x-axis
yFormat, // a format specifier string for the y-axis
xLabel, // a label for the x-axis
yLabel // a label for the y-axis
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);

// Filter undefined values.
const I = d3.range(X.length).filter(i => !isNaN(X[i]) && !isNaN(Y[i]));

// Compute the bins.
const B = d3.bin()
.thresholds(thresholds)
.value(i => X[i])
(I)
.map(bin => {
const y = i => Y[i];
const min = d3.min(bin, y);
const max = d3.max(bin, y);
const q1 = d3.quantile(bin, 0.25, y);
const q2 = d3.quantile(bin, 0.50, y);
const q3 = d3.quantile(bin, 0.75, y);
const iqr = q3 - q1; // interquartile range
const r0 = Math.max(min, q1 - iqr * 1.5);
const r1 = Math.min(max, q3 + iqr * 1.5);
bin.quartiles = [q1, q2, q3];
bin.range = [r0, r1];
bin.outliers = bin.filter(i => Y[i] < r0 || Y[i] > r1);
return bin;
});

// Compute default domains.
if (xDomain === undefined) xDomain = [d3.min(B, d => d.x0), d3.max(B, d => d.x1)];
if (yDomain === undefined) yDomain = [d3.min(B, d => d.range[0]), d3.max(B, d => d.range[1])];

// Construct scales and axes.
const xScale = xType(xDomain, xRange).interpolate(d3.interpolateRound);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(thresholds, xFormat).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));

const g = svg.append("g")
.selectAll("g")
.data(B)
.join("g");

g.append("path")
.attr("stroke", stroke)
.attr("d", d => `
M${xScale((d.x0 + d.x1) / 2)},${yScale(d.range[1])}
V${yScale(d.range[0])}
`);

g.append("path")
.attr("fill", fill)
.attr("d", d => `
M${xScale(d.x0) + insetLeft},${yScale(d.quartiles[2])}
H${xScale(d.x1) - insetRight}
V${yScale(d.quartiles[0])}
H${xScale(d.x0) + insetLeft}
Z
`);

g.append("path")
.attr("stroke", stroke)
.attr("stroke-width", 2)
.attr("d", d => `
M${xScale(d.x0) + insetLeft},${yScale(d.quartiles[1])}
H${xScale(d.x1) - insetRight}
`);

g.append("g")
.attr("fill", stroke)
.attr("fill-opacity", 0.2)
.attr("stroke", "none")
.attr("transform", d => `translate(${xScale((d.x0 + d.x1) / 2)},0)`)
.selectAll("circle")
.data(d => d.outliers)
.join("circle")
.attr("r", 2)
.attr("cx", () => (Math.random() - 0.5) * jitter)
.attr("cy", i => yScale(Y[i]));

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.call(g => g.append("text")
.attr("x", width)
.attr("y", marginBottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xLabel));

return svg.node();
}
Insert cell
import {howto, linkplot} from "@d3/example-components"
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