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).
Last active
December 26, 2015 16:49
-
-
Save matijapiskorec/7182723 to your computer and use it in GitHub Desktop.
Transitioning a network along spiral
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"> | |
<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