Built with blockbuilder.org
forked from areologist's block: demonstrates path interpolation caveat
license: mit |
Built with blockbuilder.org
forked from areologist's block: demonstrates path interpolation caveat
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="https://peterbeshai.com/d3-interpolate-path/d3-interpolate-path.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
button { | |
font-size: 16px; | |
margin: 15px; | |
padding: 10px; | |
} | |
svg { | |
border: 1px solid gray; | |
} | |
</style> | |
</head> | |
<body> | |
<button id="update">Press Me</button> | |
<script> | |
const margin = {left: 15, top: 15, right: 15, bottom: 40}; | |
const width = 960 - margin.left - margin.right; | |
const height = 500 - margin.top - margin.bottom; | |
const svg = d3.select("body").append("svg") | |
.attr("width", 960) | |
.attr("height", 500); | |
const data = Array(3).fill(0).map(generateData.bind(null, 10)); | |
const xMax = Math.max(...data.reduce((acc, x) => acc.concat([x.length - 1]), [])); | |
const xScale = d3.scaleLinear() | |
.domain([0, xMax]) | |
.range([0, width]); | |
const yScale = d3.scaleLinear() | |
.domain(d3.extent(data.reduce((acc, x) => acc.concat(x), []))) | |
.range([height, 0]); | |
const container = svg | |
.append('g') | |
.attr('transform', `translate(${margin.left}, ${margin.top})`); | |
const area = d3.area() | |
.x((d, i) => xScale(i)) | |
.y1(d => yScale(d)) | |
.y0(yScale(0)) | |
.curve(d3.curveCatmullRom); | |
const color = (i) => | |
`hsl(${Math.floor(240/data.length) * i}, 80%, 50%)`; | |
container | |
.append('clipPath') | |
.attr('id', 'rect-clip') | |
.append('rect') | |
.attr('x', 2) | |
.attr('y', -10) | |
.attr('width', width - 4) | |
.attr('height', height + 8); | |
container | |
.selectAll('path') | |
.data(data, (d, i) => i) | |
.enter() | |
.append('path') | |
.attr('clip-path', 'url(#rect-clip)') | |
.attr('d', area) | |
.attr('fill', (d, i) => color(i)) | |
.attr('fill-opacity', 0.3) | |
.attr('stroke', (d, i) => color(i)) | |
.attr('stroke-width', 2); | |
const xAxis = d3.axisBottom(xScale); | |
container | |
.append('g') | |
.attr('class', 'x-axis') | |
.attr('transform', `translate(0, ${height})`) | |
.call(xAxis); | |
function update() { | |
const x = Math.floor(Math.random() * 20) + 2; | |
data.forEach((d, i) => data[i] = generateData(x)); | |
const y = data.reduce((acc, x) => acc.concat(x), []); | |
xScale.domain([0, x - 1]); | |
yScale.domain(d3.extent(y)); | |
const excludeSegment = (a, b) => a.x === b.x && a.x === width; | |
container | |
.selectAll('path') | |
.data(data, (d, i) => i) | |
.transition() | |
.duration(500) | |
.attrTween('d', (d, i, el) => { | |
const prev = d3.select(el[i]).attr('d'); | |
const next = area(d); | |
return d3.interpolatePath(prev, next, excludeSegment); | |
}); | |
container | |
.select('.x-axis') | |
.transition() | |
.duration(500) | |
.call(d3.axisBottom(xScale)); | |
} | |
function generateData(size = 10) { | |
return Array(size).fill(0).map(() => Math.random()); | |
} | |
window.addEventListener('load', () => { | |
document.querySelector('#update') | |
.addEventListener('click', () => update()); | |
}); | |
</script> | |
</body> |