Phil Gyford
Phil Gyford

Reputation: 14594

Error when drawing a d3.js line with no data

I have a d3.js line chart, with multiple lines. Users can change filters associated with each line, which changes the values plotted, and the chart updates.

This all works fine, but if the user chooses filters which result in no matching values for a line, I get this error in the console:

Error: Invalid value for <path> attribute d=""          d3.js:8481
attrNull                                                d3.js:8481
(anonymous function)                                    d3.js:8653
d3_class.forEach                                        d3.js:275
start                                                   d3.js:8652
(anonymous function)                                    d3.js:8646
d3_timer_mark                                           d3.js:2085
d3_timer_step                                           d3.js:2065

What's visible to the user is fine - the line (obviously) doesn't draw when the chart updates. But I'd like to get rid of the error! Here's the code which enters/updates/removes the lines:

var line = g.selectAll('path.line')
            .data(function(d) { return d; },
                  function(d) { return d.id; });

line.enter().append('path')
      .attr('class', 'line')
      .attr('id', function(d) { return lineCSSID(d.id); })
      .style('stroke', function(d) { return d.color; });

line.data(function(d) { return d; })
    .transition()
    .attr('d', function(d) { return chartLine(d.values); });

line.exit().remove();

The error occurs when the return chartLine(d.values); is called and d.values is an empty array. Is there something I can do which will let it fail more gracefully?

In case it affects the answer: the user can then change that line's filters again and, if that results in there being matching data, the line will be redrawn.

Upvotes: 0

Views: 2465

Answers (3)

NickBraunagel
NickBraunagel

Reputation: 1599

I'm late to the game but consider using d3.line and declaring where the data is define with line.defined. This is a d3 built-in method to accommodating missing data.

for example:

var line = d3.line()
    .defined(function(d) { return d[hasData]; })
    .x(function(d) { return x(d.x); })
    .y(function(d) { return y(d.y); });

Where function(d) { return d[hasData]; } should return a truthy value if the object contains data, else return false. If false is returned, d3 will skip rendering that particular data element.

If your data was:

var data = [{name:"foo", value:1, hasData:true}, {name:"bar", value:0, hasData:false}]

The above setup of d3.line will skip the second object ("bar"). Also, because 0 is falsy and any positive int or float is truthy, you can simply use function(d) { return d.value; }.

Mike himself made an example here.

Cheers.

Upvotes: 0

Phil Gyford
Phil Gyford

Reputation: 14594

I have ended up by omitting any lines with no data before I add the data to the chart.

For example, if (somewhere before the code in my question) I had something like this, where data is an array of objects, each one representing a single line on the chart:

var svg = d3.select('.chart')
              .selectAll('svg')
                .data([data]);

Then before that I now do this:

data = data.filter(function(d){ return d.values.length > 0; });

This ensures that any lines with no datapoints aren't passed to the chart.

UPDATE: I realised that if all the line(s) have filters that result in no matching values, then I get errors when trying to calculate the domain for the x axis. So I've now got something like this:

if (data.length == 0) {
  // We must have filtered out all the lines because none had any values.
  // Fake an x-axis domain between 1 year ago and today.
  var to = new Date(),
      from = new Date(),
  from.setFullYear(from.getFullYear() - 1);
  xScale.domain([from, to]);

} else {
  // Standard, set the x-axis domain as usual from min/max values of all lines.
  ... 
};

xScale.range([0, width]);

Upvotes: 1

pooley1994
pooley1994

Reputation: 973

Have you tried adding some conditionality so that you only return if d.values isn't an empty array?

if(d.values.length > 0){
    return chartLine(d.values);
}

Upvotes: 0

Related Questions