Skip to content

Instantly share code, notes, and snippets.

@mbostock
Forked from mbostock/.block
Last active January 26, 2022 14:32
Show Gist options
  • Select an option

  • Save mbostock/1642989 to your computer and use it in GitHub Desktop.

Select an option

Save mbostock/1642989 to your computer and use it in GitHub Desktop.
Spline Transition

D3’s default path interpolation is the same as its string interpolation: it finds numbers embedded in strings, and interpolates those numbers. So, the default behavior when interpolating two paths is like this:

Mx0,y0Lx1,y1Lx2,y2Lx3,y3 
  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
Mx0,y1Lx1,y2Lx2,y3Lx3,y4 

For example, the first point ⟨x0,y0⟩ is interpolated to ⟨x0,y1⟩. Since x0 is the same, all you see are the y-values changing—you don't see the path slide to the left as intended.

What you want to happen here is something like this:

Mx0,y0Lx1,y1Lx2,y2Lx3,y3LxR,y4 
  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
MxL,y0Lx0,y1Lx1,y2Lx2,y3Lx3,y4 

Where xL is some negative value off the left side, and xR is some positive value off the right side. This way, the first point ⟨x0,y0⟩ is interpolated to ⟨xL,y0⟩; meaning, the x-coordinate is interpolated rather than the y-coordinate, and so the path appears to slide off to the left. Likewise, the incoming point ⟨xR,y4⟩ is interpolated to ⟨x3,y4⟩.

You can also approximate this effect by interpolating a transform attribute rather than the path, so that the path "d" attribute remains static but the path element translates to the left during the transition.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://mbostock.github.com/d3/d3.js?2.7.2"></script>
<script>
var n = 20,
random = d3.random.normal(0, .1),
data = d3.range(n).map(random);
var margin = {top: 10, right: 10, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, n - 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([-1, 1])
.range([height, 0]);
var line = d3.svg.line()
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.data([data])
.attr("class", "line")
.attr("d", line);
tick();
setInterval(tick, 1000);
function tick() {
// push a new data point onto the back
data.push(random());
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(1000)
.ease("linear")
.attr("transform", "translate(" + x(-1) + ")");
// pop the old data point off the front
data.shift();
}
</script>
@praveenuics
Copy link

praveenuics commented Jun 29, 2017

Is this code available in v3 at all?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment