I'm moving an element from one point to another. But specifically I want to achieve this animation:
http://carto.net/svg/samples/path_animation.svg
My knowledge in d3.js are somewhat limited, I do not know how to generate this curve line and I also do not know how to do 3d perspective, in which the circle should appear, then be very small and grow and eventually become small until disappearing, as well As in the attached link. How can I do it?
http://jsfiddle.net/bzfs55bg/
var circle = svg.append("circle")
.attr("fill", "blue")
.attr("r", 4)
.attr("cx", centroids.ANTIOQUIA[0])
.attr("cy", centroids.ANTIOQUIA[1]);
circle.transition()
.delay(1000)
.duration(2000)
.attr("cx", centroids.BOYACA[0])
.attr("cy", centroids.BOYACA[1]);
My answer contain 3 main steps:
First, we have to create a path from point A to point B, simulating an arc. There are several ways to do that, and your question is not clear. Here, I'm using a quadratic curve:
var arc = svg.append("path")
.style("fill", "none")
.style("stroke", "yellow")
.style("stroke-width", 2)
.attr("d", "M" + centroids.ANTIOQUIA[0] + "," +
centroids.ANTIOQUIA[1] + " Q" + centroids.BOYACA[0] +
"," + centroids.ANTIOQUIA[1] + " " +
centroids.BOYACA[0] + "," + centroids.BOYACA[1]);
This path can have a colour or be transparent, it doesn't matter.
Second, we use Bostock's famous translate along path code to translate the element along that arc. I changed the function to pass the element along with the path, so we can change its size:
circle.transition()
.duration(5000)
.attrTween("transform", translateAlong(arc.node(), circle.node()));
//the second argument is the circle -----------------^
function translateAlong(path, circle) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
d3.select(circle).attr("r", circleSize(t))
//here we can change circle's size
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
Finally, I'm using a linear scale to make the circle big and small again:
var circleSize = d3.scale.linear()
.domain([0, 0.5, 1])
.range([4, 10, 4]);
The domain here goes from 0 to 1 because this is the range of values the parameter t can assume.
Here is the updated fiddle: http://jsfiddle.net/zkc2wton/
Here is a second fiddle, changing the opacity at the beginning and at the end of the movement: http://jsfiddle.net/4pdusase/
How to draw a curve line between points:
//draw a line
var curveData = [{ x: centroids.ANTIOQUIA[0], y: centroids.ANTIOQUIA[1] }, { x: centroids.BOYACA[0], y: centroids.BOYACA[0] }];
//make a line function
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("monotone");//change it as per your choice.
//make a path
var myPath = d3.select('svg').append("path")
.attr("d", lineFunction(curveData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
Please refer interpolation for the curves you may like here
Further moving point along a line and disappearing.
//transition along a path
circle.transition()
.duration(10000)
.attrTween("transform", translateAlong(myPath.node()))
.style("opacity",0);//transitioning the opacity to make it vanish.
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
3d i don't know :(
working code here
Related
I am a beginner with d3 and currently can not use the newest version, but instead I am on version 3.x.
What I am trying to realize should be simple, but sadly I didnt find resources on how to do it:
The goal is to display a path in my svg. Then I want to display e.g. a circle and transition / move / trace the circle along the path. This works fine if I want the full path to be followed.
But the goal is to follow the path only partially.
What could I do if I want the circle to start from position 0 of the path and follow it until e.g. 25% of the path?
And then again, if the circle is at 25%, how can I follow from there to 50% of the path without starting over from position 0 of the path?
I will be very thankful for any input you can provide here. Thank you very much.
I built my answer using this code from Mike Bostock (which uses D3 v3.x, as you want): https://bl.ocks.org/mbostock/1705868
First, I created a data array, specifying how much each circle should travel along the path:
var data = [0.9, 1, 0.8, 0.75, 1.2];
The values here are in percentages. So, we have 5 circles: the first one (blue in the demo below) will stop at 90% of the path, the second one (orange) at 100%, the third one (green) at 80%, the fourth one (red) at 75% and the fifth one (coloured purple in the demo) will travel 120% of the path, that is, it will travel all the length of the path and 20% more.
Then, I changed Bostock's function translateAlong to get the datum of each circle:
function translateAlong(d, path) {
var l = path.getTotalLength() * d;
return function(d, i, a) {
return function(t) {
var p = (t * l) < path.getTotalLength() ?
path.getPointAtLength(t * l) : path.getPointAtLength(t * l - path.getTotalLength());
return "translate(" + p.x + "," + p.y + ")";
};
};
}
The important piece here is:
var l = path.getTotalLength() * d;
Which will determine the final position of each circle. The ternary operator is important because of our last circle, which will travel more than 100% of the path.
Finally, we have to call the transition, passing the datum and the path itself:
circle.transition()
.duration(10000)
.attrTween("transform", function(d) {
return translateAlong(d, path.node())()
});
Here is the demo:
var points = [
[240, 100],
[290, 200],
[340, 50],
[390, 150],
[90, 150],
[140, 50],
[190, 200]
];
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 300);
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed"));
var color = d3.scale.category10();
var data = [0.9, 1, 0.8, 0.75, 1.2];
svg.selectAll(".point")
.data(points)
.enter().append("circle")
.attr("r", 4)
.attr("transform", function(d) {
return "translate(" + d + ")";
});
var circle = svg.selectAll("foo")
.data(data)
.enter()
.append("circle")
.attr("r", 13)
.attr("fill", function(d, i) {
return color(i)
})
.attr("transform", "translate(" + points[0] + ")");
circle.transition()
.duration(10000)
.attrTween("transform", function(d) {
return translateAlong(d, path.node())()
});
// Returns an attrTween for translating along the specified path element.
function translateAlong(d, path) {
var l = path.getTotalLength() * d;
return function(d, i, a) {
return function(t) {
var p = (t * l) < path.getTotalLength() ?
path.getPointAtLength(t * l) : path.getPointAtLength(t * l - path.getTotalLength());
return "translate(" + p.x + "," + p.y + ")";
};
};
}
path {
fill: none;
stroke: #000;
stroke-width: 3px;
}
circle {
stroke: #fff;
stroke-width: 3px;
}
<script src="//d3js.org/d3.v3.min.js"></script>
here is my js fiddle: https://jsfiddle.net/DerNalia/3wzLv9yg/1/
I've been trying to interpret the code from here: Multiseries line chart with mouseover tooltip, but I just can't seem to get it working.
This is what I have so far -- it's pretty much a copy paste.
// append a g for all the mouse over nonsense
var mouseG = svg.append("g")
.attr("class", "mouse-over-effects");
// this is the vertical line
mouseG.append("path")
.attr("class", "mouse-line")
.style("stroke", "black")
.style("stroke-width", "1px")
.style("opacity", "0");
// keep a reference to all our lines
var lines = document.getElementsByClassName('line');
// here's a g for each circle and text on the line
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line");
// the circle
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return 'red';
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
// the text
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
// rect to capture mouse movements
mouseG.append('svg:rect')
.attr('width', width)
.attr('height', height)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() { // on mouse out hide line, circles and text
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() { // on mouse in show line, circles and text
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', function() { // mouse moving over canvas
var mouse = d3.mouse(this);
// move the vertical line
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + height;
d += " " + mouse[0] + "," + 0;
return d;
});
// position the circle and text
d3.selectAll(".mouse-per-line")
.attr("transform", function(d, i) {
console.log(width/mouse[0])
console.log(mouse[1]);
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) { return d.x; }).right;
idx = bisect(d.values, xDate);
// since we are use curve fitting we can't relay on finding the points like I had done in my last answer
// this conducts a search using some SVG path functions
// to find the correct position on the line
// from http://bl.ocks.org/duopixel/3824661
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
pos = lines[i].getPointAtLength(target);
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]) end = target;
else if (pos.x < mouse[0]) beginning = target;
else break; //position found
}
// update the text with y value
//d3.select(this).select('text')
// .text(y.invert(pos.y).toFixed(2));
d3.select(this).select('circle')
.attr('cy', pos.x)
.attr('cx', pos.y);
// return position
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
In case something goes wrong with the fiddle, here is what I have currently:
And here is how I would like it to appear (pardon horrible paint skills):
My issue could be related to my error as well. Cannot read property 'length' of undefined.
Updated Fiddle: https://jsfiddle.net/3wzLv9yg/2/. There are a few things that are going awry:
Line Circles
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(data)
.enter()
.append("g")
.attr("class", "mouse-per-line");
This statement adds a new g element for every data point, rather than for every line. Replace it with an array with the length of the number of lines to get an element for each line. For example replace .data(data) with .data(d3.range(lines.length)).
Multiple Techniques of Circle Location Position
It looks like you've combined two different techniques for calculating the y location of your circles, one involving calculating from data values, and the other calculating from svg elements.
The code that calculates from data values has these lines:
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) { return d.x; }).right;
idx = bisect(d.values, xDate);
There's an error in bisect(d.values, xDate); as d.values is not assigned anywhere. It should be bisect(data, xDate);, but it may be irrelevant as it isn't used anywhere else, since the rest of the function calculates y position from the svg paths. You can get rid of bisect and idx if you're using this approach:
var xDate = x.invert(mouse[0]);
Setting Location
This should alleviate console errors, but the mouse circles still do not track properly. That's because the location of the circle are set twice:
d3.select(this).select('circle')
.attr('cy', pos.x)
.attr('cx', pos.y);
// return position
return "translate(" + mouse[0] + "," + pos.y +")";
These statements sets of the g element to the correct location, but it also sets the circle to an offset of an equal amount. You only need one. Since the current implementation sets the transform of the g element, it's probably easier to keep that one and get rid of the circle offset.
// return position
return "translate(" + mouse[0] + "," + pos.y +")";
All the changes are here: https://jsfiddle.net/3wzLv9yg/2/
Here is some example code and a fiddle of it:
var w = 400;
var h = 400;
var r = 20;
var factor = 5;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")");
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", r)
.style("fill", "black");
svg.append("circle")
.attr("cx", 150)
.attr("cy", 150)
.attr("r", r)
.style("fill", "red");
svg.selectAll("circle")
.transition()
.duration(2000)
.attr("transform", "scale(" + 1/factor +")")
.attr("r", r*factor);
http://jsfiddle.net/o1wzfas7/2/
In the example, I am scaling two circles down by a factor of 5 (which also scales their positions and thus moves them "closer" to each other) and simultaneously scaling up the circles' radii by a factor of 5. The idea is that they'll appear to move closer to each other without changing size (as if I was changing their "cx" and "cy" attributes instead), but for some reason the scale transition and radius transition seem to go at different rates, so you see the circles get larger and then settle back to the initial size.
Does anybody know how I would do this using scale and radius transitions, but having the two cancel each other out so that the circles don't appear to change in size?
First, to explain what's going on:
The problem is that the changes you are making cancel out multiplicatively, but transitions proceed in an additive way.
So for your simple example, where radius (r) starts at 20, the scale (s) starts out (implicitly) as 1 and you are transitioning by a factor of 5, the effective radius of the circle is r*s:
At the start of transition:
r =20
s =1
r*s =20
At the end of transition:
r =4
s =5
r*s =20
Now, the way you're thinking of it in your head is that the factor should transition from 1 to 5, but that's not what is going to happen. The default transition functions don't see your factor, they just see that radius is transitioning from 20 to 4, and scale is transitioning from 1 to 5.
Therefore, at the midpoint of the transition, each attribute will be at the midpoint (average) of its start and end values:
r = (20+4)/2 = 12
s = (1+5)/2 = 3
r*s = 36
In order to do what you want, you're going to have to create a custom tween, which directly transitions the factor, and then calculates the radius and scale from there:
svg.selectAll("circle")
.transition()
.duration(2000)
.tween("factor", function(d,i){
/* create an interpolator for your factor */
var f = d3.interpolateNumber(1,factor);
/* store the selected element in a variable for easy modification */
var c = d3.select(this);
/* return the function which will do the updates at each tick */
return function(t) {
var f_t = f(t);
c.attr("transform", "scale(" + 1/f_t + ")" );
c.attr("r", r*f_t );
};
});
Note that in your real application, you'll need to store the "start" value for your factor transition in a global variable or each data object, since it won't automatically be 1 when you transition to a different scaling factor.
var w = 400;
var h = 400;
var r = 20;
var factor = 5;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w/2 + "," + h/2 + ")");
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", r)
.style("fill", "black");
svg.append("circle")
.attr("cx", 150)
.attr("cy", 150)
.attr("r", r)
.style("fill", "red");
svg.selectAll("circle")
.transition()
.duration(2000)
.tween("factor", function(d,i){
/* create an interpolator for your factor */
var f = d3.interpolateNumber(1,factor);
/* store the selected element in a variable for easy modification */
var c = d3.select(this);
/* return the function which will do the updates at each tick */
return function(t) {
var f_t = f(t);
c.attr("transform", "scale(" + 1/f_t + ")" );
c.attr("r", r*f_t );
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I'm still learning to program and I'm currently trying out the d3 library.
So far I'm pretty happy with the result. fiddle
Q: If you check out the link (or part of the code under this question) you should try to plot a point. This is only possible on the x-axis ticks. You'll see it animates but it's not exactly what I want. I just want it to animate the newly added line. I have checked out .enter() and .append() but I was getting errors. I might be doing something wrong.
function lines(x, y) {
this.x = x;
this.y = y+h;
}
var lineArray = [{x: 0, y: h}, {x: 1, y: h}];
var lineArrayPrevious = lineArray[lineArray.length -1].x;
var d3line = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("monotone");
var path = svg.append("path").attr("d", d3line(lineArray)).attr("class", "line");
canPlot = true;
function plot() {
var m = d3.mouse(this);
if (m[0]-20 > lineArray[lineArray.length - 1].x) {
var lineX = lineArray.push(new lines(m[0], m[1]));
svg.selectAll("path")
.data(lineArray)
.attr("d", d3line(lineArray));
var point = svg.append("circle")
.attr("cx", function(d, i) { return m[0]; })
.attr("cy", function(d, i) { return m[1]+h; })
.attr("r", 0).transition().delay(150).attr("r", 6);
var totalLength = path.node().getTotalLength();
console.log();
path.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition().duration(700).ease("linear").attr("stroke-dashoffset", 0).delay(200);
canPlot = true;
} else { console.log("error"); canPlot = false; }
}
Excuse my bad code, I'm learning and will clean it up eventually.
Q2: How hard would it be to make a circle that follows the mouse's y-position and moves on the ticks when you get near one?
Q3: If we solve my first question, would it be easy to get the lines to animate/update automatically when we do question 2?
Thanks in advance.
I've updated your jsfiddle here to include the points that you're asking for.
Regarding question 1, I've changed the way the line is drawn such that it can be interpolated from the previous to the current point in a transition. The relevant code is this.
svg.select("path.line")
.attr("d", d3line(lineArray))
.transition().duration(700)
.attrTween('d', pathTween)
.each("end", function() {
var lineX = lineArray.push(new lines(m[0], m[1]));
});
var last = lineArray[lineArray.length-1];
function pathTween() {
var xi = d3.interpolate(last.x, m[0]),
yi = d3.interpolate(last.y, m[1] + h);
return function(t) {
return d3line(lineArray.concat([{x: xi(t), y: yi(t)}]));
};
}
Note that the new data point is only added to the array of points once the transition finishes.
Regarding your second question, this is taken care of by attaching handlers to all tick marks and append a marker on mouse over:
d3.selectAll(".xaxis > .tick").on("mouseenter", mousein)
.on("mousemove", mousemove)
.on("mouseleave", mouseout);
function mousein() {
svg.append("circle").attr("class", "marker").attr("r", 3)
.attr("pointer-events", "none");
}
function mousemove() {
d3.select("circle.marker")
.attr("transform", d3.select(this).attr("transform"))
.attr("cy", d3.mouse(this)[1] + h);
}
function mouseout() {
d3.select("circle.marker").remove();
}
I'm creating a modified version of Mike Bostock's hierarchical edge bundling diagram:
http://mbostock.github.com/d3/talk/20111116/bundle.html
but I want to make arcs which span certain groups of data, like this:
I'm currently just hardcoding the length of the arc, but I want to do it dynamically. How can I accomplish this? Here's my current code:
/* MH - USER DEFINED VARIABLES */
var chartConfig = { "Tension" : .85, "canvasSize" : 800, "dataFile" : "../data/projects.json", "linePadding" : 160, "textPadding" : 30, "arcPadding" : 5, "arcWidth" : 30 }
var pi = Math.PI;
var radius = chartConfig.canvasSize / 2,
splines = [];
var cluster = d3.layout.cluster() //Cluster is the diagram style, a node to link dendrogram dendrogram (tree diagram)
.size([360, radius - chartConfig.linePadding]); //MH - sets the size of the circle in relation to the size of the canvas
var bundle = d3.layout.bundle(); //Bundles the node link lines so that they spread at the end but keep close initially
var arcInner = radius - chartConfig.linePadding + chartConfig.arcPadding;
var arcOuter = arcInner + chartConfig.arcWidth;
var arc = d3.svg.arc().innerRadius(arcInner).outerRadius(arcOuter);
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(chartConfig.Tension) //How tightly to bundle the lines. No tension creates straight lines
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var vis = d3.select("#chart").append("svg")
.attr("width", radius * 2)
.attr("height", radius * 2)
.attr("class","svg")
.append("g")
.attr("class","chart")
.attr("transform", "translate(" + radius + "," + radius + ")");
d3.json(chartConfig.dataFile, function(classes) {
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = vis.selectAll ("path.link")
.data(links)
.enter().append("path")
.attr("class", function(d){ return "link source-" + d.source.key + " target-" + d.target.key; })
.attr("d", function(d,i){ return line(splines[i]); });
vis.selectAll("g.node")
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("g")
.attr("class", "node")
.attr("id",function(d){ return "node-" + d.key; })
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.append("text")
.attr("dx", function(d) { return d.x < 180 ? chartConfig.textPadding : -chartConfig.textPadding; }) //dx Moves The text out away from the lines in a positive or negative direction, depending on which side of the axis it is on
.attr("dy", ".31em") //moves the text up or down radially around the circle
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) {
textString = d.key;
textString = textString.split('_').join(' '); //MH replace underscores with spaces
return textString;
})
.on("mouseover",textOver)
.on("mouseout",textOut);
});
/* ARCS ARE HARDCODED, SHOULD BE DYNAMIC */
var arcData = [
{aS: 0, aE: 45,rI:radius - chartConfig.linePadding + chartConfig.arcPadding,rO:radius - chartConfig.linePadding + chartConfig.textPadding-chartConfig.arcPadding}
];
var arcJobsData = d3.svg.arc().innerRadius(arcData[0].rI).outerRadius(arcData[0].rO).startAngle(degToRad(1)).endAngle(degToRad(15));
var g = d3.select(".chart").append("svg:g").attr("class","arcs");
var arcJobs = d3.select(".arcs").append("svg:path").attr("d",arcJobsData).attr("id","arcJobs").attr("class","arc");
g.append("svg:text").attr("x",3).attr("dy",15).append("svg:textPath").attr("xlink:href","#arcJobs").text("JOBS").attr("class","arcText"); //x shifts x pixels from the starting point of the arc. dy shifts the text y units from the top of the arc
...
function degToRad(degrees){
return degrees * (pi/180);
}
function updateNodes(name,value){
return function(d){
if (value) this.parentNode.appendChild(this);
vis.select("#node-"+d[name].key).classed(name,value);
}
}
I've seen your json data structure here: http://mikeheavers.com/transfers/projects/data/projects.json. Firstly, in order to group the data and append the tag correctly, it'll be better to change your data like this: https://raw.github.com/gist/4172625/4de3e6a68f9721d10e0068d33d1ebb9780db4ae2/flare-imports.json to create a hirarchical structure.
We can then use the groups to draw the arcs.
First we create groups by "selectAll" and filter your nodes. Here you could add other group names of your data:
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {return (d.key=='Jobs' || d.key == 'Freelance' || d.key == 'Bayard') && d.children; }))
.enter().append("group")
.attr("class", "group");
I just checked that in my case, so you'd better verify the result of the filter and make change according to your case (our data structure is a little bit different).
Now we got a list of groups. Then we'll go through the children of each group, and choose the smallest and largest x as the start and end angle. We can create a function like this:
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d){
if (d.x < min)
min = d.x;
});
return degToRad(min);
}
And similarly a findEndAngle function by replacing min by max. Then we can create the arcs' format:
var groupArc = d3.svg.arc()
.innerRadius(arcData[0].rI)
.outerRadius(arcData[0].rO)
.startAngle(function(d){return findStartAngle(d.children);})
.endAngle(function(d){return findEndAngle(d.children);});
Then we can create arcs in "dynamic" way:
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("arc")
.attr("d", groupArc)
.attr("class", "arc")
.append("svg:text")
...;
In my case it is groupData[0], maybe you should check it in your case.
For adding tags to arcs you just need to add d.key or d.name according to the result of your selection.
The full code is available here: https://gist.github.com/4172625. Every time I get json from database so if there's no dynamic way to generic arcs I will be dead :P Hope it helps you!