Public
Edited
Jun 9
19 stars
Plot: A few custom time axesAccess to Family planningPlot: MarimekkoAutoplot matrixWhat do people buy on Bandcamp?Plot: colorContrast transformA Timeline of Democratic Presidential CandidatesPlot: Diverging stacked barsPlot: Horizon ChartPlot: Ridgeline PlotTwo-Tone Pseudo Color Scales with Observable Plot & Vega-LiteRecreating Östling’s regression visualizationsError barsPlot: regressionSkies PlotHorizon graph & Barcode with PlotTemporal AliasingPlot for mathematiciansPlot Isotype dot plotCOVID-19 Netherlands Reproduction Number TutorialPlot Mountain SunsetSpine chartsGreenhouse gas emission projectionsRaincloud Plots with Observable PlotUpset Plots with Observable PlotExploring CIELChabNature variant waves graphicWhen Presidents Fade AwayThe Coronavirus landscapeOur World In Data charts visualisationThe normal modelVisualizing The New York Times’s Mini CrosswordDistributions and summary statistics - a collection of Plot examplesVariants of SARS-Cov-2 in EuropeLaid off: 6 million jobs and countingConcentration values vs. Time
Topological subsampling
Plot of plotsCyclical time scale, v2Bullet graph IIPlot: Bullet graphCyclical time scaleRoad traffic under COVID restrictions (France)K-means binningMatrix diagramELD ViewerHappy anniversary, Project Gutenberg!ROC curvePlot stargazer historyPlot HyperboloidPhone bar plotsLog-scale histogramDatabase Outage PlotBertin’s hotelIreland’s top trade partnersBump chartBubble MatrixRenko ChartGitHub BurndownPlot: Grid choroplethCandlestick Chart
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = () => {
const chart = Plot.plot({
facet: { data: weather, y: "origin" },
//grid: true,
nice: true,
width,
height: 750,
fy: { padding: 0.15 },
x: {
axis: "top",
tickFormat: (d) =>
`${Plot.formatMonth()(d.getUTCMonth())} ${
d.getUTCMonth() === 0 ? d.getUTCFullYear() : ""
}`,
ticks: 12,
grid: true
},
y: { insetTop: 10, insetBottom: 25, grid: false },
marks: [
Plot.frame({ strokeWidth: 0.5 }),

Plot.lineY(
weather,
Plot.select(
{ y: toposimplify({ sample, include_ends: true, take: "high" }) },
{
x: "date",
y: "temp",
stroke: "orange",
curve: "monotone-x",
strokeWidth: 1
}
)
),
Plot.lineY(
weather,
Plot.select(
{ y: toposimplify({ sample, include_ends: true, take: "low" }) },
{
x: "date",
y: "temp",
stroke: "lightblue",
curve: "monotone-x",
strokeWidth: 1
}
)
),

// autolabel
Plot.text(
weather,

// This map allows to compute texts only on the few selected values
// It would also allow, if we wanted, to add "MAX" to the maximum value, etc
Plot.map(
{
text: (data) =>
data.map(
(d) =>
`${d3.utcFormat("%b %-e\n%H:00")(
d.date - 4 * 3600 * 1000
)}\n${(+d.temp).toFixed(1)}°F\n↓`
)
},
Plot.select(
{
y: toposimplify({
sample: labels >> 1,
include_ends: false,
take: "high"
})
},
{
x: "date",
y: "temp",
z: "origin",
text: weather,
dx: 0,
dy: -3,
stroke: "white",
fill: "black",
lineAnchor: "bottom",
lineHeight: 1.2
}
)
)
),

// lower temps autolabel
Plot.text(
weather,
Plot.select(
{
y: toposimplify({
sample: labels >> 1,
include_ends: false,
take: "low"
})
},
{
x: "date",
y: "temp",
z: "origin",
text: (d) =>
`↑\n${d3.utcFormat("%b %e\n%H:00")(
d.date - 4 * 3600 * 1000
)}\n${(+d.temp).toFixed(1)}°F`.replace(" ", " "),
dx: 0,
dy: 3,
stroke: "white",
fill: "black",
lineAnchor: "top",
lineHeight: 1.2
}
)
),

Plot.dot(weather, {
x: "date",
y: "temp",
z: "origin",
curve: "linear",
fill: "black",
r: 0.5
})
]
});

return chart;
}
Insert cell
toposimplify = ({ sample = 3, include_ends = false, take = "both" } = {}) =>
(index, values) => {
// filter out invalid values
index = index.filter((i) => values[i] === +values[i]);
// Assumes the index is sorted on the x axis
const position = new Map(Array.from(index, (i, j) => [i, j]));
const sub = topoline(Array.from(index, (i) => values[i]));
const selection = d3.sort(
d3
.sort(sub, (d) => -d.persistence)
.slice(0, sample)
.flatMap(({ low, high }) =>
take === "both" ? [low, high] : take === "high" ? [high] : [low]
)
.map((j) => index[j]),
(i) => position.get(i) // Maintain positional order
);
if (include_ends) {
if (selection[0] !== index[0]) selection.unshift(index[0]);
if (selection[selection.length - 1] !== index[index.length - 1])
selection.push(index[index.length - 1]);
}
return selection;
}
Insert cell
topoline = function (series) {
const E_insert = 0;
const E_left = 1;
const E_right = 2;
const E_merge = 3;

const n = series.length;
const tree = new Int32Array(n).fill(-1);
const features = [];
const order = d3.sort(
d3.range(0, tree.length),
(i) => series[i],
(i) => i
);

let event;
for (const i of order) {
const left = tree[i - 1] ?? tree[i];
const right = tree[i + 1] ?? tree[i];

if (left === -1 && right === -1) {
// insert leaf
tree[i] = i;
event = E_insert;
} else if (left === -1) {
tree[i] = right;
event = E_right;
} else if (right === -1) {
tree[i] = left;
event = E_left;
} else {
// merge node
const [a, b0] = d3.sort(
[tree[left], tree[right]],
(i) => series[i],
(i) => i
);
const b = i === order[order.length - 1] ? a : b0;
features.push({
low: b,
high: i,
persistence: series[i] - series[b],
error: error(b, i)
});
for (let j = i - 1; tree[j] === b; j--) tree[j] = a;
for (let j = i + 1; tree[j] === b; j++) tree[j] = a;
tree[i] = a;
event = E_merge;
}
}

// 🌶 ensure we have global min and max: if we didn't end on a merge event,
// it means that the maximum value was reached either on the left or on the right
if (event === E_left || event === E_right) {
const low = order[0];
const high = order.pop();
features.push({
low,
high,
persistence: series[high] - series[low],
error: error(low, high)
});
}

return d3.sort(features, (d) => -d.persistence);

function error(b, i) {
return d3.sum(d3.range(...d3.extent([i, b])), (k) =>
Math.abs(
(series[b] - series[i]) * ((k - i) / (b - i)) - (series[k] - series[i])
)
);
}
}
Insert cell
weather = FileAttachment("weather.csv").csv({ typed: true })
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