Published
Edited
Feb 17, 2022
12 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const path = svg
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 8)
.attr("d", line(data));

const pathB = svg
.append("path")
.attr("fill", "none")
.attr("stroke", "lime")
.attr("stroke-width", 2),
pathC = svg
.append("path")
.attr("fill", "none")
.attr("stroke", "orange")
.attr("stroke-width", 2);

const points = svg
.append("g")
.selectAll("circle")
.data(data.slice(0, Math.ceil(n + 1)))
.join("circle")
.attr("r", 4)
.style("stroke", "white")
.style("fill", "black")
.attr("cx", d => d[0])
.attr("cy", d => d[1]);

pathB.attr(
"d",
lineM(
[data[0]]
.concat(data)
.concat([data[data.length - 1]])
.slice(0, 2 + Math.floor(n + 1))
)
);
pathC.attr(
"d",
lineM(
[data[0]]
.concat(data)
.concat([data[data.length - 1]])
.slice(0, 2 + Math.ceil(n + 1))
)
);

// We're using two paths drawn with a *curveCardinalOpen* on the same data with fake ends.
// These paths are almost exactly superimposed with our *curveCardinal* path, so we can
// measure them and interpolate!
const lB = pathB.node().getTotalLength();
const lC = pathC.node().getTotalLength();
const l = lB + (lC - lB) * (n - Math.floor(n));
// debug
pathC.attr("stroke-dasharray", [0, lB, lC - lB]);

// finally, apply a stroke-dasharray of the correct length
path.attr("stroke-dasharray", [l, path.node().getTotalLength() - l]);

return svg.node();
}
Insert cell
Insert cell
function measureCardinal(points, n) {
points = [points[0]].concat(points).concat([points[points.length - 1]]);
const pathB = d3.create("svg:path"),
pathC = d3.create("svg:path");

return n === undefined ? l : l(n);

function l(n) {
pathB.attr("d", lineM(points.slice(0, 2 + Math.floor(n + 1)))),
pathC.attr("d", lineM(points.slice(0, 2 + Math.ceil(n + 1))));
const lB = pathB.node().getTotalLength();
const lC = pathC.node().getTotalLength();
return lB + (lC - lB) * (n - Math.floor(n));
}
}
Insert cell
Insert cell
{
const context = DOM.context2d(width, height);
const line = d3
.line()
.curve(d3.curveCardinal)
.context(context);
const l = measureCardinal(data);

context.lineWidth = 30;

let l0 = 0,
l1,
total = l(data.length);
for (let n = 0; n < data.length; n += .005) {
l1 = l(n);
context.beginPath();
line(data);
context.strokeStyle = d3.interpolateRainbow(n - Math.floor(n));
context.setLineDash([0, l0, l1 - l0, total]);

context.stroke();
yield context.canvas;
await visibility();

l0 = l1;
}
}
Insert cell
Insert cell
{
const context = DOM.context2d(width, height);

const line = d3
.line()
.curve(d3.curveCardinal)
.context(context);
const l = measureCardinal(data);

context.lineWidth = 0;

let l0 = 0,
l1,
total = l(data.length);
let steps = 0;
for (let n = 0; n < data.length; n += .005) {
l1 = l(n);
context.beginPath();
line(data);

context.lineWidth = context.lineWidth * (1 - 0.01) + 0.01 * 10 * (l1 - l0);

context.strokeStyle = d3.interpolateRainbow(n - Math.floor(n));
context.setLineDash([0, Math.max(l0 - 2, 0), 2 + l1 - l0, total]);

context.stroke();
if (steps++ % 50 === 0) yield context.canvas;

await visibility();

l0 = l1;
}
}
Insert cell
Insert cell
data = Array.from({ length: 25 }, () => [
40 + (width - 80) * Math.random(),
40 + (height - 80) * Math.random()
])
Insert cell
line = d3.line().curve(d3.curveCardinal)
Insert cell
lineM = d3.line().curve(d3.curveCardinalOpen)
Insert cell
d3 = require("d3@7")
Insert cell
import { Scrubber } from "@mbostock/scrubber"
Insert cell
height = 500
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