Reputation: 7215
I'm having a go at my first line chart in d3.js and would like to draw the path between the data points gradually, but also display each data point as a solid circle.
I have already got this working, but what I would really like to try and do is show each circle appear as the line hits each data point.
Right now the data points appear first and then the line path is drawn between them as per this snippet of my code:
var path = g.append("svg:path")
.attr("d", line(data));
var totalLength = path.node().getTotalLength();
console.log(totalLength);
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.attr("stroke-dashoffset", 0);
Working example - http://codepen.io/anon/pen/JYqPvZ
Is what I'm trying to achieve possible?
Thanks
Upvotes: 1
Views: 2662
Reputation: 108512
You could write a custom tween function:
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
// custom tween
.tween("line", function() {
// set up an interp function
var interp = d3.interpolateNumber(totalLength, 0);
var self = d3.select(this);
// this is called on each animation frame
return function(t) {
// calculate offset and apply it
var offset = interp(t);
self.attr("stroke-dashoffset", offset);
// calculate current x position of line
var xPos = this.getPointAtLength(totalLength - offset).x;
// for each point see if we can "show" the circle
g.selectAll(".point").each(function(){
var point = d3.select(this);
if (xPos > (+point.attr('cx'))){
point.style('opacity',1);
}
})
};
});
Working code:
var data = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 7],
w = 600,
h = 400,
margin = 20,
y = d3.scale.linear().domain([0, d3.max(data)]).range([0 + margin, h - margin]),
x = d3.scale.linear().domain([0, data.length]).range([0 + margin, w - margin]);
var vis = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
var g = vis.append("svg:g")
.attr("transform", "translate(0, 400)");
var line = d3.svg.line()
//.interpolate("cardinal")
.x(function(d,i) { return x(i); })
.y(function(d) { return -1 * y(d); });
var path = g.append("svg:path")
.attr("d", line(data));
var totalLength = path.node().getTotalLength();
console.log(totalLength);
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.tween("line", function() {
var interp = d3.interpolateNumber(totalLength, 0);
var self = d3.select(this);
return function(t) {
var offset = interp(t);
self.attr("stroke-dashoffset", offset);
var xPos = this.getPointAtLength(totalLength - offset).x;
g.selectAll(".point").each(function(){
var point = d3.select(this);
if (xPos > (+point.attr('cx'))){
point.style('opacity',1);
}
})
};
});
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -1 * y(0))
.attr("x2", x(w))
.attr("y2", -1 * y(0))
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -1 * y(0))
.attr("x2", x(0))
.attr("y2", -1 * y(d3.max(data)));
g.selectAll(".xLabel")
.data(x.ticks(5))
.enter().append("svg:text")
.attr("class", "xLabel")
.text(String)
.attr("x", function(d) { return x(d) })
.attr("y", 0)
.attr("text-anchor", "middle")
g.selectAll(".yLabel")
.data(y.ticks(4))
.enter().append("svg:text")
.attr("class", "yLabel")
.text(String)
.attr("x", 0)
.attr("y", function(d) { return -1 * y(d) })
.attr("text-anchor", "right")
.attr("dy", 4)
g.selectAll(".xTicks")
.data(x.ticks(5))
.enter().append("svg:line")
.attr("class", "xTicks")
.attr("x1", function(d) { return x(d); })
.attr("y1", -1 * y(0))
.attr("x2", function(d) { return x(d); })
.attr("y2", -1 * y(-0.3))
g.selectAll(".yTicks")
.data(y.ticks(4))
.enter().append("svg:line")
.attr("class", "yTicks")
.attr("y1", function(d) { return -1 * y(d); })
.attr("x1", x(-0.3))
.attr("y2", function(d) { return -1 * y(d); })
.attr("x2", x(0))
var points = g.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("class","point")
.attr("stroke", "steelblue")
.attr("fill", function(d, i) { return "steelblue" })
.attr("cx", function(d, i) { return x(i); })
.attr("cy", function(d, i) { return -1 * y(d); })
.attr("r", function(d, i) { return 5 })
.style("opacity", 0)
.on('click', function(p){
console.log('clicked! - ' + p );
})
.on("mouseover", function(d) {
d3.select(this).attr("r", 8).style("fill", "red").attr("stroke", "red");
})
.on("mouseout", function(d) {
d3.select(this).attr("r", 5).style("fill", "steelblue").attr("stroke", "steelblue");
});
var val = points.append('g');
val.selectAll('text')
.data(function(d,i){ return d.values})
.enter().append('text')
.attr("x", function(d, i) {
return x(i) + x.rangeBand() / 2;})
.attr("y", function(d, i) { return y(d.value) })
.attr('dy', -10)
.attr("text-anchor", "middle")
.text(function(d) { return d.value; })
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
line {
stroke: black;
}
text {
font-family: Arial;
font-size: 9pt;
}
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
Upvotes: 1