const d3 = window.d3; const appDiv = document.getElementById("app"); const parentStyle = ` display: flex; width: 900px; height: 500px; justify-content: center; align-items: center; font-family: sans-serif; font-weight: 100; `; const width = 400; const height = 400; const childStyle = ` width: ${width}px; height: ${height}px; `; appDiv.innerHTML = `
Before
After
`; const points = [ { group: "a", x: 3, y: 2 }, { group: "a", x: 6, y: 8 }, { group: "a", x: 8, y: 1 }, { group: "b", x: 1, y: 5 }, { group: "b", x: 9, y: 6 } ]; const xExtent = [0, 10]; const xRange = [0, width]; const xScale = d3 .scaleLinear() .domain(xExtent) .range(xRange); const yExtent = [0, 10]; const yRange = [height, 0]; const yScale = d3 .scaleLinear() .domain(yExtent) .range(yRange); const line = d3 .line() .x(d => xScale(d.x)) .y(d => yScale(d.y)); const color = group => (group === "a" ? "blue" : "red"); const opacity = 0.3; const nested = d3 .nest() .key(d => d.group) .entries(points) .map(point => { return { ...point, pathString: line(point.values) }; }); const paths = nested.map(point => { const { key } = point; const pathStyle = ` fill: none; stroke: ${color(point.key)}; opacity: ${opacity}; `; const html = ``; return { ...point, html }; }); const circles = points.map(point => { const { x, y, group } = point; const transform = `translate(${xScale(x)}, ${yScale(y)})`; const style = `fill: ${color(group)}; opacity: ${opacity}`; return ` `; }); // Based on: https://gist.github.com/mbostock/4163057 // Sample the SVG path string "d" uniformly with the specified precision. function getPoints(d, step) { var path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", d); var length = path.getTotalLength(); return d3.range(0, length, step).map(function(t) { var point = path.getPointAtLength(t); return { x: point.x, y: point.y, t: t / length }; }); } const chart = (div, withExtraPoints) => { let allPoints = points.map(point => { const arr = [xScale(point.x), yScale(point.y)]; arr.group = point.group; return arr; }); if (withExtraPoints) { const linePoints = paths .map(path => { const { pathString, key } = path; const extraPoints = getPoints(pathString, 20) .filter(p => p.x && p.y) .map(point => { const arr = [point.x, point.y]; arr.group = key; return arr; }); return extraPoints; }) .flat(); allPoints = allPoints.concat(linePoints); } const delaunay = d3.Delaunay.from(allPoints); const voronoi = delaunay.voronoi(); const cells = allPoints.map((point, i) => { const cellStyle = ` fill: none; stroke: black; pointer-events: all; opacity: 0.2; `; const { group } = point; const d = voronoi.renderCell(i); if (!d) { return ""; } return ``; }); const childNode = div; childNode.node().innerHTML = ` ${paths.map(p => p.html).join("")} ${circles.join("")} ${cells.join("")} `; div .selectAll(".cell") .on("mouseover", function() { const { group } = this.dataset; childNode.selectAll(`.data`).classed("hover", false); childNode.selectAll(`.group-${group}`).classed("hover", true); }) .on("mouseout", function() { childNode.selectAll(`.data`).classed("hover", false); }); }; const before = d3.select("#before"); const after = d3.select("#after"); chart(before); chart(after, true);