3

I have separate x and y arrays and want to connect the dots using a line path. This seems to be about the simplest possible example but I don't quite grok the writing the function. Here is my code:

<!DOCTYPE html>
<meta charset="utf-8">

<body>
<script src = "http://d3js.org/d3.v3.min.js"> </script>
<script>

var margin = {top: 20, right: 20, bottom: 20, left: 20},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;

var xdata = d3.range(20);
var ydata = [1, 4, 5, 9, 10, 14, 15, 15, 11, 10, 5, 5, 4, 8, 7, 5, 5, 5, 8, 10];

var xscl = d3.scale.linear()
    .domain(d3.extent(xdata))
    .range([0, width])

var yscl = d3.scale.linear()
    .domain(d3.extent(ydata))
    .range([height, 0])

var slice = d3.svg.line()
    .x(function(d) { return xscl(xdata[d]);})
    .y(function(d) { return yscl(ydata[d]);})

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

svg.append("path")
    .attr("class", "line")
    .attr("d", slice)

</script>
</body>

But it returns an error Uncaught TypeError: Cannot read property 'length' of undefined, so clearly the function returned by d3.svg.line() doesn't have the right form. What's wrong? I pray not a typo!

3 Answers 3

7

I know it has been more than a year, but I had to deal with this problem also. Storing path data (x, y) in 2 separate arrays is much more memory efficient than the 2D array d3.svg.line expects. For a very large number of points, the accepted answer is also inefficient by looping through all elements to create the 2D array.

The solution I found without adding any loops is write a wrapper function for d3.svg.line as follows:

var line = function(x, y){
    return d3.svg.line()
    .x(function(d,i) { return x[i]; }) 
    .y(function(d,i) { return y[i]; })
    (Array(x.length));
}

and then set the path attributes:

svg.append("path")
    .attr("d", line(x_array, y_array))

See the updated fiddle here

Sign up to request clarification or add additional context in comments.

Comments

4

Based on Elijah's spot on remark about d3.svg.line, I think it is hard to go about this without putting the array as expected by this function. So:

var xy = [];
for(var i=0;i<xdata.length;i++){
   xy.push({x:xdata[i],y:ydata[i]});
}

I made other changes regarding .domain and the slice function per se. Here is a FIDDLE with the results of my effort.

3 Comments

Thank you. I removed the i from the d3.svg.line function. I'm coming from R and the JS data structures are givimg me hell. They are hard to read and seem to be overly complex. I guess d3.js wants data in this particular format pretty regularly, which is fine, but I haven't found where this is documented. I've either missed it or it's assumed, which is tough on noobies. Thanks much, I've learned something very useful here.
YMW. D3 has its fair amount of challenges...and we all love it :)
I put the final version in gist with additional comments. It might help others who are learning the d3 way of thinking.
2

d3.svg.line can only take one data source. However, you can feed it your two data sources by putting them into an object:

newData = {x: xdata, y: ydata};

 var slice = d3.svg.line()
  .x(function(d,i) { return xscl(d.xdata[i]);})
  .y(function(d,i) { return yscl(d.ydata[i]);})

Then point your line function at newData and you should be set:

 svg.append("path")
  .attr("class", "line")
  .attr("d", slice(newData))

Typically, though, you're better off building an array of coordinate pairs, since that's what it's expecting.

2 Comments

Would you mind looking at this fiddle ? The path is being created, but has no data points.
Looks like slice(xy) is null.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.