chart = {
const margin = {top: 70, right: 40, bottom: 40, left: 40};
const svg_width = width + margin.left + margin.right;
const svg_height = height + margin.top + margin.bottom;
const svg = d3.create("svg").attr("width", svg_width).attr("height", svg_height);
const chart = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
const x = d3.scaleUtc()
.domain(d3.extent(data, d => d[dateColumn]))
.range([0, width]);
chart.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));
const y = d3.scaleLinear()
.domain(d3.extent(data, d => d[valueColumn])).nice()
.range([height, 0]);
chart.append("g")
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -30)
.attr("y", -25)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(`↑ ${yAxisLabel}`));
const points = data.map(d => [d[dateColumn], d[valueColumn], d[nameColumn]]);
const groups = d3.rollup(points, v => Object.assign(v, {z: v[0][2]}), d => d[2]);
const line = d3.line()
.x(d => x(d[0]))
.y(d => y(d[1]));
const color = d3.scaleOrdinal()
.domain(d3_unique(points, d => d[2]))
.range(d3[interpolationSelected]);
const legend = Swatches(color);
const path = chart.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.selectAll("path")
.data(groups.values())
.join("path")
.attr("stroke", d => color(d.z))
// .attr("stroke", d => color.domain().includes(d[2]) ? color(d[2]) : "steelblue")
.attr("d", line);
const dot = chart.append("g")
.attr("display", "none");
dot.append("circle").attr("r", 2.5);
dot.append("text").attr("text-anchor", "middle");
chart.on("pointerenter", pointerenter)
.on("pointermove", pointermove)
.on("pointerleave", pointerleave);
function pointerenter() {
path.style("mix-blend-mode", null).style("stroke", "#ddd");
dot.attr("display", null);
}
function pointermove(event) {
const [xm, ym] = d3.pointer(event);
const i = d3.leastIndex(points, ([d, v]) => Math.hypot(x(d) - xm, y(v) - ym));
const [d, v, k] = points[i];
path.style("stroke", ({z}) => z === k ? null : "#ddd").filter(({z}) => z === k).raise();
dot.attr("transform", `translate(${x(d)}, ${y(v)})`)
.select("text")
.call(text => text
.selectAll("tspan")
.data([k, formatDate(d), v])
.join("tspan")
.attr("x", 0)
.attr("y", (_, i) => `${i * 1.1 - 4}em`)
.attr("font-size", "1em")
.attr("font-weight", (_, i) => i ? null : "bold")
.style("text-anchor", "middle")
.text(d => d));
}
function pointerleave() {
path.style("mix-blend-mode", "multiply").style("stroke", null);
dot.attr("display", "none");
}
// Add highlight region
chart.append("g")
.selectAll("rect")
.data(highlightedPeriods)
.join("rect")
.attr('x', d => x(d[0]))
.attr('y', 0)
.attr('width', d => x(d[1]) - x(d[0]))
.attr('height', height)
.attr('stroke', 'grey')
.attr('fill', 'grey')
.attr('opacity', 0.2);
return Object.assign(svg.node(), {legend: legend});
}