This example illustrates progressive line animations in D3. This example applies to situations in which you want to add data to the front of a line or remove data from the end of a line. In this case, the transition is isolated to the data that changes and does not apply to the entire line.
Partial Line Transitions in D3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
svg { | |
font: 10px sans-serif; | |
} | |
.line { | |
fill: none; | |
stroke: #de2d26; | |
stroke-width: 2px; | |
} | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #969696; | |
shape-rendering: crispEdges; | |
} | |
text { | |
color: #636363; | |
} | |
#variables { | |
font-size: 200%; | |
} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
var margin = {top: 20, right: 20, bottom: 20, left: 40}, | |
width = 1000 - margin.left - margin.right, | |
height = 500 - margin.top - margin.bottom; | |
var x = d3.scale.linear() | |
.domain([-1, 1]) | |
.range([0, width/2]); | |
var y = d3.scale.linear() | |
.domain([-1, 1]) | |
.range([height, 0]); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient("bottom") | |
.tickValues([-1, -.5, .5, 1]) | |
.tickSize(3, 6); | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("left") | |
.tickValues([-1, -.5, .5, 1]) | |
.tickSize(3, 6); | |
var line = d3.svg.line() | |
.x(function(d, i) { return x(d[0]); }) | |
.y(function(d, i) { return y(d[1]); }); | |
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," + y(0) + ")") | |
.call(xAxis); | |
svg.append("g") | |
.attr("class", "y axis") | |
.attr("transform", "translate(" + x(0) + ",0)") | |
.call(yAxis); | |
svg.append("g") | |
.attr("id", "variables") | |
.attr("transform", "translate(650,0)"); | |
svg.select("#variables") | |
.append("text") | |
.attr("id", "type") | |
.attr("transform", "translate(0,10)"); | |
svg.select("#variables") | |
.append("text") | |
.attr("id", "t") | |
.text("t: ") | |
.attr("transform", "translate(0,50)"); | |
svg.select("#variables") | |
.append("text") | |
.attr("id", "point_a") | |
.text("point_a:") | |
.attr("transform", "translate(0,90)"); | |
svg.select("#variables") | |
.append("text") | |
.attr("id", "point_i") | |
.text("point_i:") | |
.attr("transform", "translate(0,130)"); | |
svg.select("#variables") | |
.append("text") | |
.attr("id", "point_b") | |
.text("point_b:") | |
.attr("transform", "translate(0,170)"); | |
window.onload=enqueue('dev'); | |
function random() { | |
var num = Math.random(); | |
return ((num - .5)*2).toFixed(2); | |
}; | |
function random_array() { | |
return [random(), random()] | |
}; | |
function create_queue(id, data) { | |
svg.append("g") | |
.append("path") | |
.data([data]) | |
.attr("class", "line") | |
.attr("id", id) | |
.attr("d", line); | |
enqueue(id); | |
} | |
function enqueue(id) { | |
// display | |
svg.select("#type").text("function: enqueue"); | |
// assign/select unique id to enable multiple paths | |
var target = "#" + id, | |
path = svg.select(target); | |
if ( path.empty() ) { | |
// create new queue | |
create_queue(id, d3.range(5).map(random_array)); | |
} else { | |
// push point into rear of queue | |
path.data()[0].push([random(), random()]); | |
// redraw the line | |
path | |
.transition() | |
.duration(4000) | |
.ease("linear") | |
.attrTween('d', enqueueInter()); | |
} | |
} | |
function enqueueInter() { | |
return function (d, i, a) { | |
var point_a = d[d.length-2], | |
point_b = d[d.length-1]; | |
// display | |
svg.select("#point_a").text("point_a: " + point_a); | |
svg.select("#point_b").text("point_b: " + point_b); | |
var interpolateX = d3.scale.linear() | |
// time value is between 0 and 1 | |
.domain([0,1]) | |
// coerce numbers between starting and ending x value | |
.range([point_a[0], point_b[0]]); | |
return function(t) { | |
while(t != 1) { | |
// display | |
svg.select("#t").text("t: " + t.toFixed(2)) | |
// cut data to static coords | |
var interpolatedLine = d.slice(0, d.length-1), | |
interpolatedY = point_b[1] * t + point_a[1] * (1-t), | |
point_i = [interpolateX(t), interpolatedY]; | |
// add interpolated coord to rear | |
interpolatedLine.push(point_i); | |
//display | |
svg.select("#point_i").text("point_i: " + parseFloat(point_i[0]).toFixed(2) + "," + parseFloat(point_i[1]).toFixed(2)) | |
// draw interpolated line | |
return line(interpolatedLine); | |
} | |
// draw final line | |
dequeue('dev'); | |
return line(d) | |
} | |
} | |
} | |
function dequeue(id) { | |
// display | |
svg.select("#type").text("function: dequeue"); | |
// assign/select unique id to enable multiple paths | |
var query = "#" + id, | |
path = svg.select(query); | |
if ( path.data()[0].length == 1 ) { | |
// remove queue | |
path.remove(); | |
} else { | |
path | |
.transition() | |
.duration(4000) | |
.ease("linear") | |
.attrTween('d', dequeueInter()); | |
} | |
} | |
function dequeueInter() { | |
return function (d, i, a) { | |
var point_a = d[0], | |
point_b = d[1]; | |
// display | |
svg.select("#point_a").text("point_a: " + point_a); | |
svg.select("#point_b").text("point_b: " + point_b); | |
var interpolateX = d3.scale.linear() | |
// time value is between 0 and 1 | |
.domain([0,1]) | |
// coerce numbers between starting and ending x value | |
.range([point_a[0], point_b[0]]); | |
return function(t) { | |
while(t != 1) { | |
// display | |
svg.select("#t").text("t: " + t.toFixed(2)) | |
// cut data to static coords | |
var interpolatedLine = d.slice(1, d.length), | |
interpolatedY = point_b[1] * t + point_a[1] * (1-t), | |
point_i = [interpolateX(t), interpolatedY]; | |
//display | |
svg.select("#point_i").text("point_i: " + parseFloat(point_i[0]).toFixed(2) + "," + parseFloat(point_i[1]).toFixed(2)) | |
// add interpolated coord to front | |
interpolatedLine.unshift(point_i); | |
// draw interpolated line | |
return line(interpolatedLine); | |
} | |
// remove coord from front | |
d.shift() | |
enqueue('dev'); | |
// draw final line | |
return line(d); | |
} | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment