Reputation: 376
I am very new to D3, and I wanted to make a line with multiple, evenly spaced arrows running along said line to indicate the flow of a circuit.
This is basically what I'm going for (with the arrows animated along the line in an infinite loop) (link in comment, not high enough reputation)
I found this great example of an image animated to follow a path with correct rotation. http://bl.ocks.org/KoGor/8163268
My problem is that I don't know how to place all the extra arrows on to my path. I considered breaking up my path into many equal length paths in a group end to end and animating them all at once, but that seemed more complicated than it needed to be.
Any idea how I should proceed?
Here is what I have so far: https://jsfiddle.net/singerbradley/wcfg2mec/16/
var points = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(1) // Catmull–Rom
.interpolate("linear")); //basis-open
var arrow = svg.append("polygon")
.attr("points", "0,24, 15,12, 0,0") // x,y points
.attr("transform", "translate(" + points[3] + ")");
transition();
function transition() {
arrow.transition()
.duration(10000)
.ease("linear")
.attrTween("transform", translateAlong(path.node()))
.each("end", transition); //infinite loop
}
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
var t0 = 0;
return function(d, i, a) {
return function(t) {
var p0 = path.getPointAtLength(t0 * l); //previous point
var p = path.getPointAtLength(t * l); //current point
var angle = Math.atan2(p.y - p0.y, p.x - p0.x) * 180 / Math.PI;//angle for tangent
t0 = t;
//Shifting center to center of arrow
// xoffset and yoffset should be half the original width and height
var xoffset = 12, yoffset = 12;
var centerX = p.x - xoffset;
var centerY = p.y - yoffset;
return "translate(" + centerX + "," + centerY + ")rotate(" + angle + " " + xoffset + " " + yoffset + ")";
};
};
}
path {
fill: none;
stroke: #000;
stroke-width: 1px;
}
polygon {
fill: steelblue;
stroke: #fff;
stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Upvotes: 1
Views: 892
Reputation: 102219
There are several ways for doing this. Here is my solution.
First, if you want 10 arrows, let's create the data array with 10 elements:
var arrowData = d3.range(10);
And append the arrows accordingly:
var arrow = svg.selectAll(".arrow")
.data(arrowData)
.enter()
.append("polygon")
.attr("points", "0,24, 15,12, 0,0");
Then, at every second, we'll call transition()
for a different arrow, using an IIFE with a setTimeout
:
(function loop() {
if (counter++ > 8) return;
setTimeout(function() {
var thisPolygon = d3.selectAll("polygon").filter(function(d, i) {
return i == counter;
});
transition(thisPolygon);
loop()
}, 1000)
}());
For this to work, we slightly modify the transition
function:
function transition(elem) {
elem.transition()
.duration(10000)
.ease("linear")
.attrTween("transform", translateAlong(path.node()))
.each("end", function() {
return transition(elem)
});
}
Here is your updated fiddle: https://jsfiddle.net/3o7vzvfa/. Here is another one, with 50 arrows: https://jsfiddle.net/buLjg7d3/
And here the same code in a Stack snippet:
var points = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var arrowData = d3.range(10);
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(1) // Catmull–Rom
.interpolate("linear")); //basis-open
var arrow = svg.selectAll(".arrow")
.data(arrowData)
.enter()
.append("polygon")
.attr("points", "0,24, 15,12, 0,0");
var counter = -1;
(function loop() {
if (counter++ > 8) return;
setTimeout(function() {
var thisPolygon = d3.selectAll("polygon").filter(function(d, i) {
return i == counter;
});
transition(thisPolygon);
loop()
}, 1000)
}())
function transition(elem) {
elem.transition()
.duration(10000)
.ease("linear")
.attrTween("transform", translateAlong(path.node()))
.each("end", function() {
return transition(elem)
}); //infinite loop
}
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
var t0 = 0;
return function(d, i, a) {
return function(t) {
var p0 = path.getPointAtLength(t0 * l); //previous point
var p = path.getPointAtLength(t * l); //current point
var angle = Math.atan2(p.y - p0.y, p.x - p0.x) * 180 / Math.PI; //angle for tangent
t0 = t;
//Shifting center to center of arrow
// xoffset and yoffset should be half the original width and height
var xoffset = 12,
yoffset = 12;
var centerX = p.x - xoffset;
var centerY = p.y - yoffset;
return "translate(" + centerX + "," + centerY + ")rotate(" + angle + " " + xoffset + " " + yoffset + ")";
};
};
}
path {
fill: none;
stroke: #000;
stroke-width: 1px;
}
polygon {
fill: steelblue;
stroke: #fff;
stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Upvotes: 3