Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing SVG path length in d3

Tags:

d3.js

I've put together a multi line chart which has a transition set so the lines are 'drawn' and labels attached one after the other. It works but I can't see how to access the length of each line individually. The code that I have returns the length for the first path only via .node(). The result of this is that all paths are given the length of the first which gives incorrect start points for the other paths. The code is below. Any help is much appreciated.

var margin = {top: 20, right: 80, bottom: 30, left: 60},
    width = 600 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .rangePoints([0, width], 1);

var y = d3.scale.linear()
    .range([height, 0]);

var color = d3.scale.category20();

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var line = d3.svg.line()
    .interpolate("linear")
    .x(function(d) { return x(d.month); })
    .y(function(d) { return y(d.rainfall); });

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.csv("data.csv", function(error, data) {
    color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Month"; }));

var countries = color.domain().map(function(country) {
    return {
        country: country,
        values: data.map(function(d) {
            return {month: d.Month, rainfall: +d[country]};
        })
    };
});

x.domain(data.map(function(d) { return d.Month; }));
y.domain([
    d3.min(countries, function(c) { return d3.min(c.values, function(v) { return v.rainfall - 7; }); }),
    d3.max(countries, function(c) { return d3.max(c.values, function(v) { return v.rainfall + 7; }); })
]);

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", "1.1em")
    .style("text-anchor", "end")
    .text("Rainfall (mm)");

var country = svg.selectAll(".country")
.data(countries)
  .enter().append("g")
    .attr("class", "country");

var path = country.append("path")
    .attr("id", function(d, i) { return "line" + i; })
    .attr("class", "line")
    .attr("d", function(d) { return line(d.values); })
    .style("stroke", function(d) { return color(d.country); })

var totalLength = path.node().getTotalLength();

path.attr("stroke-dasharray", totalLength + " " + totalLength)
    .attr("stroke-dashoffset", totalLength)
  .transition()
    .delay(function(d, i) { return i * 1000; })
    .duration(1000)
    .ease("linear")
    .attr("stroke-dashoffset", 0)
    .each("end", function() {
        labels.transition()
            .delay(function(d, i) { return i * 1000; })
            .style("opacity", 1);
        });

var labels = country.append("text")
    .datum(function(d) { return {country: d.country, value: d.values[d.values.length - 1]}; })
    .attr("class", "label")
    .attr("transform", function(d) { return "translate(" + x(d.value.month) + "," + y(d.value.rainfall) + ")"; })
    .attr("x", 3)
    .attr("dy", ".35em")
    .style("opacity", 0)
    .text(function(d) { return d.country; });

});

like image 272
user3194692 Avatar asked Feb 15 '23 02:02

user3194692


1 Answers

You could add the total length of the path as another attribute to the data and then use that:

path.each(function(d) { d.totalLength = this.getTotalLength(); })
    .attr("stroke-dasharray", function(d) { return d.totalLength + " " + d.totalLength; })
    .attr("stroke-dashoffset", function(d) { return d.totalLength; })
// etc
like image 99
Lars Kotthoff Avatar answered May 24 '23 02:05

Lars Kotthoff