Skip to content

Instantly share code, notes, and snippets.

@EmbraceLife
Last active July 29, 2016 03:41
Show Gist options
  • Save EmbraceLife/c7f0e41f3d23758815d1d77e7856793a to your computer and use it in GitHub Desktop.
Save EmbraceLife/c7f0e41f3d23758815d1d77e7856793a to your computer and use it in GitHub Desktop.
Line Transition1
license: gpl-3.0

This example is the second of three in the Path Transitions tutorial; see the previous example for context.

The desired pairing of numbers for path interpolation is like this:

M x0, y0 L x1, y1 L x2, y2 L x3, y3 L xR, y4
   ↓   ↓    ↓   ↓    ↓   ↓    ↓   ↓
M xl, y0 L x0, y1 L x1, y2 L x2, y3 L x3, 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⟩.

While you could write a custom interpolator and use transition.attrTween to achieve this, a much simpler solution is to interpolate the transform attribute rather than the path. This way, the shape of the path remains static while the it translates left during the transition.

Immediately prior to the transition, the path is redrawn as follows:

M x0, y0 L x1, y1 L x2, y2 L x3, y3 L xr, y4

Then, a transform transition is applied:

translate(0,0)
          ↓
translate(xl,0)

This causes the path to slide left. A clip path is used so the path is not visible outside of the chart body.

Note that for charts with spline interpolation, you’ll need to crop the visible part of the line by an extra point, so that the change in tangent is not visible; see the next example.

forked from mbostock's block: Line Transition

<!DOCTYPE html>
<meta charset="utf-8">
<script src="//d3js.org/d3.v4.min.js"></script>
<!-- what to achieve? ----------------------------------
1. create a line
2. transition to push new data into line data
3. remove the begining data each time when pushing new data
-->
<!---- step 1: mannually create a svg with height and width -->
<svg width="960" height="500"></svg>
<style>
/* ---- step 2: set styles ------------------------------
1. className: "line"
2. set fill color, stroke color, and stroke-width
*/
.line {
fill: none;
stroke: #000;
stroke-width: 1.5px;
}
</style>
<script>
// ---- step 3: create inner canvas placeholder g ---------------
// 1. select svg
// 2. set margin for centering inner canvas
// 3. set inner canvas' width and height
// 4. upon svg, create a g placeholder
// 5. add transform-translate attr: move g to origin position of inner canvas
// or move g to topLeft corner of inner canvas
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 20, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// ---- step 4: create x,y-axis scale-map functions ------------------
// 1. set n: num of ticks on x-axis
// 2. scaleLinear for x:
// 2.1 set domain: 0 to n-1
// 2.2 set range: 0 to width
// 3. scaleLinear for y:
// 3.1 set domain: -1 to 1
// 3.2 set range: height to 0
var n = 40;
var x = d3.scaleLinear()
.domain([0, n - 1])
.range([0, width]); // [0, innerwidth]
var y = d3.scaleLinear()
.domain([-1, 1])
.range([height, 0]); // upside down for canvas
// ---- step 5: create line ----------------
// 1. set line function
// 2. set line.x()
var line = d3.line()
// define x values and y values
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
// The <defs> tag is short for definitions and contains definition of special elements (such as gradients).
var rects = g.append("defs")
// Clipping is about hiding what normally would be drawn. The stencil which defines what is and what isn't drawn is called a clipping path
.append("clipPath")
.attr("id", "clip")// add id attr
.append("rect")
.attr("width", width)
.attr("height", height);
// by now, under inner canvas g, create a tree: defs, clipPath, rect with inner width and height
// console.log(rects);
// under inner canvas g, create another g for x-axis
g.append("g")
.attr("class", "axis axis--x")
// for y.domain() is [-1,1], range is [height,0]
// y(0) is height/2
.attr("transform", "translate(0," + y(0) + ")")
.call(d3.axisBottom(x));
// under inner canvas g, create another g for y-axis
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y));
// ---- create data -----------
// get population mean at 0, deviation at 0.2
var random = d3.randomNormal(0, .2);
var data = d3.range(n).map(random);// random is a function
// get array from 0 to 39
// console.log(d3.range(40));
// // get 40 random numbers with 0 mean and 0.2 divation
// console.log(data);
// under inner canvas g, create another g with clip-path attr
g.append("g")
.attr("clip-path", "url(#clip)")
// upon which, create a child path
.append("path")
.datum(data) // bind data using .datum
.attr("class", "line") // add class line attr
// do transition upon path above
.transition()
.duration(5000)// last 0.5 second each
.ease(d3.easeLinear)// do linear ease effect
.on("start", tick);
function tick() {
// Push a new data point onto the back.
data.push(random());
// Redraw the line.
d3.select(this)
.attr("d", line)
.attr("transform", null);
// Slide it to the left.
d3.active(this)
.attr("transform", "translate(" + x(-1) + ",0)")
.transition()
.on("start", tick);
// Pop the old data point off the front.
data.shift();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment