user3194692
user3194692

Reputation: 105

Accessing SVG path length in d3

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; });

});

Upvotes: 5

Views: 10729

Answers (2)

Jerónimo Ekerdt
Jerónimo Ekerdt

Reputation: 131

The Lars Kotthoff answer works perfectly, anyway, when you are coding using typescript, you might face many errors due to the type of the datum. To avoid this just call this.getTotalLength() on any function that you need, it's not just available on the each(...) function. Or just assign it to a variable

Upvotes: 0

Lars Kotthoff
Lars Kotthoff

Reputation: 109292

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

Upvotes: 10

Related Questions