Public
Edited
Feb 9, 2024
Insert cell
Insert cell
cars
Insert cell
{
const v1 = (d) => d.sales;
const v2 = (d) => d.efficiency;
//const y2 = d3.scaleLinear(d3.extent(cars, v2), [0, d3.max(cars, v1)]);
const y2 = d3.scaleLinear(d3.extent(cars, v2), d3.extent(cars, v1));

return Plot.plot({
x: { tickFormat: "" }, // no comma for years
y: {
axis: "left",
label: "sales (M)",
//transform: (d) => d / 1e6,
ticks: 10,
tickFormat: "s",
domain: d3.extent(cars, v1)
},
marks: [
Plot.axisY(y2.ticks(), {
color: "steelblue",
anchor: "right",
label: "efficiency (mpg)",
y: y2,
tickFormat: y2.tickFormat()
}),
Plot.ruleY([0]),
Plot.lineY(cars, { x: "year", y: v1 }),
Plot.lineY(
cars,
Plot.mapY((d) => d.map(y2), { x: "year", y: v2, stroke: "steelblue" })
)
]
});
}
Insert cell
Insert cell
withZoom = {
const zoomExtent = [
[-20, -20],
[950, 320] // empirical... 🤔
];

const vd = (d) => d.year;
const vLeft = (d) => d.sales;
const vRight = (d) => d.efficiency;
const sl = d3.scaleLinear();
const xDataExtent = d3.extent(cars, vd);
const yLeftDataExtent = d3.extent(cars, vLeft);
const yRightDataExtent = d3.extent(cars, vRight);

//console.log({ yLeftDataExtent, yRightDataExtent });

const renderPlot = (xDomain, yDomain, ticks) => {
const y2 = d3.scaleLinear(yRightDataExtent, yLeftDataExtent);

const marks = [
Plot.axisY(y2.ticks(ticks), {
color: "steelblue",
anchor: "right",
label: "efficiency (mpg)",
y: y2,
tickFormat: ".1f",
ticks
}),
Plot.lineY(cars, { x: "year", y: vLeft, tip: true }),
Plot.lineY(
cars,
Plot.mapY((d) => d.map(y2), {
x: "year",
y: vRight,
stroke: "steelblue",
tip: true
})
)
];

return Plot.plot({
width: 700,
height: 300,
x: { domain: xDomain, tickFormat: "", ticks: 10 },
y: {
axis: "left",
label: "sales (M)",
ticks: 10,
tickFormat: "s",
domain: yDomain
},
grid: true,
tooltip: true,
marks
});
};
const insertPlot = (xDomain, yDomain, ticks) => {
const chart = renderPlot(xDomain, yDomain, ticks);
div.html("").append(() => chart);
return [chart.scale("x"), chart.scale("y")];
};

const zoomed = (e) => {
//console.log(e.transform);

const x = sl.domain(xScale.domain).range(xScale.range);
const xDomain = e.transform.rescaleX(x).domain();

const y = sl.domain(yScale.domain).range(yScale.range);
const yDomain = e.transform.rescaleY(y).domain();

const ticks = Math.round(10 * e.transform.k);

insertPlot(xDomain, yDomain, ticks);
};

const div = d3.create("div");
const [xScale, yScale, legend] = insertPlot(xDataExtent, yLeftDataExtent, 10);

//console.log({ xScale, yScale });
//console.log("xDataExtent", xDataExtent);
//console.log("yLeftDataExtent", yLeftDataExtent);
//console.log("xScale.apply(x0)", xScale.apply(xDataExtent[0]));
//console.log("xScale.apply(x1)", xScale.apply(xDataExtent[1]));
//console.log("yScale.apply(y0)", yScale.apply(yLeftDataExtent[0]));
//console.log("yScale.apply(y1)", yScale.apply(yLeftDataExtent[1]));

const zoom = d3
.zoom()
.translateExtent(zoomExtent)
.scaleExtent([1, 10])
.on("zoom", zoomed);
div.call(zoom);

return div.node();
}
Insert cell
cars = FileAttachment("new-passenger-cars.csv").csv({typed: true})
Insert cell
d3 = require("d3@7")
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