Unlisted
Edited
Sep 23, 2024
49 forks
Importers
60 stars
Insert cell
Insert cell
Insert cell
chart = IndexChart(indices, {
x: d => d.Date,
y: d => d.Close,
z: d => d.Symbol,
yLabel: "↑ Change in price (×)",
width,
height: 600
})
Insert cell
indices = FileAttachment("indices.csv").csv({typed: true})
Insert cell
Insert cell
date = Generators.input(chart) // or say viewof date = IndexChart(…)
Insert cell
Insert cell
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/index-chart
function IndexChart(data, {
x = ([x]) => x, // given d in data, returns the (temporal) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
z = () => 1, // given d in data, returns the (categorical) z-value for series
defined, // for gaps in data
curve = d3.curveLinear, // how to interpolate between points
marginTop = 20, // top margin, in pixels
marginRight = 40, // right margin, in pixels
marginBottom = 30, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
xType = d3.scaleUtc, // the x-scale type
xDomain, // [xmin, xmax]
xRange = [marginLeft, width - marginRight], // [left, right]
xFormat, // a format specifier string for the x-axis
yType = d3.scaleLog, // the y-scale type
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
yFormat = "", // a format specifier string for the y-axis
yLabel, // a label for the y-axis
zDomain, // array of z-values
formatDate = "%b %-d, %Y", // format specifier string for dates (in the title)
colors = d3.schemeTableau10, // array of categorical colors
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);
const Z = d3.map(data, z);
if (defined === undefined) defined = (d, i) => !isNaN(X[i]) && !isNaN(Y[i]);
const D = d3.map(data, defined);

// Compute default x- and z-domains, and unique the z-domain.
if (xDomain === undefined) xDomain = d3.extent(X);
if (zDomain === undefined) zDomain = Z;
zDomain = new d3.InternSet(zDomain);

// Omit any data not present in the z-domain.
const I = d3.range(X.length).filter(i => zDomain.has(Z[i]));
const Xs = d3.sort(I.filter(i => D[i]).map(i => X[i])); // for bisection later

// Compute default y-domain.
if (yDomain === undefined) {
const r = I => d3.max(I, i => Y[i]) / d3.min(I, i => Y[i]);
const k = d3.max(d3.rollup(I, r, i => Z[i]).values());
yDomain = [1 / k, k];
}

// Construct scales and axes.
const xScale = xType(xDomain, xRange).interpolate(d3.interpolateRound);
const yScale = yType(yDomain, yRange);
const color = d3.scaleOrdinal(zDomain, colors);
const xAxis = d3.axisBottom(xScale).ticks(width / 80, xFormat).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(null, yFormat);

// Construct formats.
formatDate = xScale.tickFormat(null, formatDate);

// Construct a line generator.
const line = d3.line()
.defined(i => D[i])
.curve(curve)
.x(i => xScale(X[i]))
.y((i, _, I) => yScale(Y[i] / Y[I[0]]));

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;")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.on("touchstart", event => event.preventDefault())
.on("pointermove", pointermoved);

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

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

const rule = svg.append("g");

rule.append("line")
.attr("y1", marginTop)
.attr("y2", height - marginBottom - 15)
.attr("stroke", "currentColor");

const ruleLabel = rule.append("text")
.attr("y", height - marginBottom - 15)
.attr("fill", "currentColor")
.attr("text-anchor", "middle")
.attr("dy", "1em");

const serie = svg.append("g")
.selectAll("g")
.data(d3.group(I, i => Z[i]))
.join("g");

serie.append("path")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke", ([z]) => color(z))
.attr("d", ([, I]) => line(I));

serie.append("text")
.attr("font-weight", "bold")
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 3)
.attr("stroke-linejoin", "round")
.attr("x", ([, I]) => xScale(X[I[I.length - 1]]))
.attr("y", ([, I]) => yScale(Y[I[I.length - 1]] / Y[I[0]]))
.attr("dx", 3)
.attr("dy", "0.35em")
.text(([z]) => z)
.clone(true)
.attr("fill", ([z]) => color(z))
.attr("stroke", null);

function update(date) {
date = Xs[d3.bisectCenter(Xs, date)];
rule.attr("transform", `translate(${xScale(date)},0)`);
ruleLabel.text(formatDate(date));
serie.attr("transform", ([, I]) => {
const i = I[d3.bisector(i => X[i]).center(I, date)];
return `translate(0,${yScale(1) - yScale(Y[i] / Y[I[0]])})`;
});
svg.property("value", date).dispatch("input", {bubbles: true}); // for viewof
}

function pointermoved(event) {
update(xScale.invert(d3.pointer(event)[0]));
}

update(xDomain[0]);

return Object.assign(svg.node(), {scales: {color}, update});
}
Insert cell
import {howto, altplot} from "@d3/example-components"
Insert cell
import {Swatches} from "@d3/color-legend"
Insert cell
Insert cell
trigger = {
d3.select(chart)
.on("pointerenter", () => d3.select(chart).interrupt())
.transition()
.ease(d3.easeCubicOut)
.duration(2000)
.tween("date", () => {
const i = d3.interpolateDate(...d3.extent(indices, d => d.Date).reverse());
return t => chart.update(i(t));
});
}
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