Skip to content

Instantly share code, notes, and snippets.

@matijapiskorec
Last active December 26, 2015 16:49
Show Gist options
  • Save matijapiskorec/7182723 to your computer and use it in GitHub Desktop.
Save matijapiskorec/7182723 to your computer and use it in GitHub Desktop.
Transitioning a network along spiral

Repeated transitions of points connected into a network along the Archimede's spiral. Point's color transition from blue to red the further they are from the center (measured along the spiral).

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<style>
path {
fill: none;
stroke: #000;
stroke-width: 2px;
}
.randomCircle {
fill: black;
}
.link {
fill: none;
stroke: #999;
stroke-width: 1px;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//The SVG Container
var width = 960;
var height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// Parameters for the spiral along which circles will move
var loops = 5;
var numAnchorPoints = 200;
var a = 6; // Scaling of the spiral
var numRandomPoints = 10;
// Scale for mapping integer numbers 1..numAnchorPoints to radians of the spiral
var scale = d3.scale.linear()
.domain([0,numAnchorPoints])
.range([0,loops*2*Math.PI]);
// Archimedes spiral
var spiral = function(t){
return {"x":a*scale(t)*Math.cos(scale(t)),
"y":a*scale(t)*Math.sin(scale(t))};
};
// Just a range of integers 1..numAnchorPoints
var lineData = d3.range(numAnchorPoints);
// Accessor function that will calculate svg path of the spiral
var lineFunction = d3.svg.line()
.x(function(d) { return spiral(d).x; })
.y(function(d) { return spiral(d).y; })
.interpolate("basis"); // try with "linear" or "basis"
// Draw the spiral using the accessor function
var path = svg.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 1)
.attr("fill", "none")
.attr("transform", "translate(" + width/2 + "," + height/2+ ")");
// Generate some radnom points on the curve
var spiralLength = path.node().getTotalLength();
// Random circles somewhere on the spiral
var randomNumbers = d3.range(numRandomPoints).map(function(x){return Math.random();});
var links = [[0,1],[3,2],[1,6],[2,7],[1,4],[6,3],[2,8],[1,9],[5,4],[0,7],[2,9]];
var mappedLinks = links.map(function(d){return [randomNumbers[d[0]],randomNumbers[d[1]]];});
svg.selectAll(".link")
.data(mappedLinks)
.enter().append("line")
.classed("link",true)
.attr("x1", function(d) { return path.node().getPointAtLength(d[0]*spiralLength).x; })
.attr("y1", function(d) { return path.node().getPointAtLength(d[0]*spiralLength).y; })
.attr("x2", function(d) { return path.node().getPointAtLength(d[1]*spiralLength).x; })
.attr("y2", function(d) { return path.node().getPointAtLength(d[1]*spiralLength).y; })
.attr("transform", function() { return "translate(" + width/2 + "," + height/2+ ")"});
// .attr("stroke-dasharray", "3,3");
// Circles closer to the center are bluer
svg.selectAll(".randomCircle")
.data(randomNumbers)
.enter().append("circle")
.classed("randomCircle",true)
.attr("r", 5)
.style("fill",function(d){return d3.rgb(d*255,0,255*(1-d));})
.attr("transform", function(d) { return "translate(" + path.node().getPointAtLength(d*spiralLength).x +
"," + path.node().getPointAtLength(d*spiralLength).y + ")" +
", translate(" + width/2 + "," + height/2+ ")"});
// Smooth transition of random points along the curve
transition();
setInterval( transition, 5000 );
function transition() {
// We have to explicitly store old data values because there is no way to retrieve them inside transition
// and we need them so that we know starting values of each of the circles for the custom tween transition.
// We can retrieve atribute value (translate(...) in our case) but from it we cannot retrieve position on the path.
var oldRandomNumbers = d3.selectAll(".randomCircle").data();
var newRandomNumbers = d3.range(numRandomPoints).map(function(x){return Math.random();});
var newMappedLinks = links.map(function(d){return [newRandomNumbers[d[0]],newRandomNumbers[d[1]]];});
var oldMappedLinks = links.map(function(d){return [oldRandomNumbers[d[0]],oldRandomNumbers[d[1]]];});
d3.selectAll(".link")
.data(newMappedLinks)
.transition()
.duration(5000)
.attrTween("x1", function(d,i,a) { return function(t) { return path.node().getPointAtLength((oldMappedLinks[i][0]+t*(d[0]-oldMappedLinks[i][0]))*spiralLength).x; } })
.attrTween("y1", function(d,i,a) { return function(t) { return path.node().getPointAtLength((oldMappedLinks[i][0]+t*(d[0]-oldMappedLinks[i][0]))*spiralLength).y; } })
.attrTween("x2", function(d,i,a) { return function(t) { return path.node().getPointAtLength((oldMappedLinks[i][1]+t*(d[1]-oldMappedLinks[i][1]))*spiralLength).x; } })
.attrTween("y2", function(d,i,a) { return function(t) { return path.node().getPointAtLength((oldMappedLinks[i][1]+t*(d[1]-oldMappedLinks[i][1]))*spiralLength).y; } });
d3.selectAll(".randomCircle")
.data(newRandomNumbers)
.transition()
.duration(5000)
.attrTween("transform", translateAlong(oldRandomNumbers, path.node()))
.style("fill",function(d){return d3.rgb(d*255,0,255*(1-d));});
}
// Returns an attrTween for translating along the specified path element.
function translateAlong(oldRandomNumbers, path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
// We want to interpolate between the old and the new parametric value
var p = path.getPointAtLength( (oldRandomNumbers[i]+t*(d-oldRandomNumbers[i])) * l );
return "translate(" + p.x + "," + p.y + "), translate(" + width/2 + "," + height/2+ ")";
};
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment