Published
Edited
Jun 9, 2022
1 fork
1 star
Insert cell
Insert cell
chart = {
// create the SVG
const svg = d3.create('svg')
.attr('height', height)
.attr('width', width)
.attr('font-family', 'sans-serif')
.attr('font-size', 12)
const brush = d3.brushY().extent([
[-(brushWidth / 2), margin.top],
[brushWidth / 2, height - margin.bottom]
])
.on("start brush end", brushed);
// create the x-scale for the axes
const axisSpacing = width / axisNames.length;
// const x = d3.scalePoint(axisNames, [margin.left + axisSpacing / 2, width - margin.right - axisSpacing / 2]);
const x = d3.scalePoint()
.domain(axisNames)
.range([margin.left + axisSpacing / 2, width - margin.right - axisSpacing / 2]);

// create the y-scale for each axis
const y = new Map(Array.from(axisNames, axisName => [axisName, d3.scaleLinear(d3.extent(data, d => d[axisName]), [margin.top, height - margin.bottom])]));

// the line function
const line = d3.line()
.defined(([, value]) => value != null)
.x(([axisName]) => x(axisName))
.y(([axisName, value]) => y.get(axisName)(value));

// draw lines
const path = svg.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-opacity", 0.4)
.selectAll("path")
.data(data)
.join("path")
.attr("stroke", selectedColor)
.attr("d", d => line(d3.cross(axisNames, [d], (axisName, d) => [axisName, d[axisName]])));

// draw axes
svg.append("g")
.selectAll("g")
.data(axisNames)
.join("g")
.attr("transform", d => `translate(${x(d)}, 0)`)
.each(function(d) {
d3.select(this).call(d3.axisLeft(y.get(d)));
})
.call(g => g.append("text")
.attr("y", height - 6)
.attr("text-anchor", 'middle')
.attr("font-weight", "bold")
.attr("fill", '#000')
.text(d => d))
.call(g => g.selectAll("text")
.clone(true).lower()
.attr("fill", "none")
.attr("stroke-width", 5)
.attr("stroke-linejoin", "round")
.attr("stroke", "white"))
.call(brush);
const selections = new Map();

// brushing function for each axis
function brushed({selection}, axisName) {
if (selection === null) {
selections.delete(axisName);
} else {
selections.set(axisName, selection.map(y.get(axisName).invert));
}
const selected = [];
path.each(function(d) {
const active = Array.from(selections).every(([axisName, [min, max]]) => d[axisName] >= min && d[axisName] <= max);
d3.select(this).style("stroke", active ? selectedColor : unselectedColor);
if (active) {
d3.select(this).raise();
selected.push(d);
}
});
svg.property("value", selected).dispatch("input");
}
return svg.node();
}
Insert cell
margin = ({top: 20, right: 10, bottom: 20, left: 10})
Insert cell
height = 500
Insert cell
brushWidth = 30;
Insert cell
selectedColor = "steelblue"
Insert cell
unselectedColor = "#ddd"
Insert cell
data = FileAttachment("cars_full.csv").csv({typed: true});
Insert cell
data.columns
Insert cell
// first two axes are categorical and not yet supported by this chart
axisNames = data.columns.slice(2)
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