Skip to content

d3_svg_lineLinear — Allow null values for path points #583

Closed
kitmonisit opened this Issue Mar 6, 2012 · 9 comments

2 participants

@kitmonisit

I'm working with data that sometime have holes in them. I think it would be useful to suppress those missing data. I made an attempt to do just that with the linear interpolator:

function d3_svg_lineLinear(points) {
  var i = 0,
      n = points.length,
      p = points[0],
      path = [p[0], ",", p[1]];
  while (++i < n) {
      if (points[i][1]) {
        path.push("L", (p = points[i])[0], ",", p[1]);
      } else if (points[i+1][1]) {
        path.push("M", (p = points[i+1])[0], ",", p[1]);
      } else {
        path.push("M", (p = points[i])[0], ",", 0);
      }
  }
  return path.join("");
}

It works pretty well with just a line generator:

Line

But breaks with area generator:

Area

I could give this a shot. Will it be feasible to begin work on this? I think that, for this to work with the area generator, several areas will need to be created, instead of just one.

@mbostock
Owner
mbostock commented Mar 6, 2012

Interesting idea, and more convenient than the current approach using d3.split, since you still only need a single path element for the area or line.

This definitely looks possible, but I'd detect nulls at a higher level: at the data object rather than at the points. I'd modify d3_svg_linePoints so that it returns an array of arrays of points (each inner array representing a contiguous section). Then you should be able to modify the line and area to compute the paths for each section separately, with the appropriate "M" moveto command at the beginning of each.

@kitmonisit

Yes, I actually return nulls from the object level, like this:

var line = d3.svg.line()
    .x(function(d) { 
       var date = new Date(d[0]);
       return x(date);
    })
    .y(function(d) {
       var height = d[1]; 
       if (some_missing_condition) {
           return null;
       }
       return y(height) + 0.5;

I think your approach is doable. Just a quick overview since you're more familiar with the codebase, I'm looking at the following TODO's:

  1. d3_svg_linePoints should return an array of arrays of points.
  2. d3_svg_lineLinear will iterate over that array of arrays, sticking an M at the beginning of each sub-array, join them all, and return a string suitable for the browser.

I'm looking to contribute this part, but I'm not familiar with testing. Will that be all right?

@kitmonisit

Here's a quick hack as a proof of concept: commit 6916278266

@mbostock
Owner
mbostock commented Mar 7, 2012

I see. That's different than what I suggested, which is to detect nulls in the data array directly (before they are passed to the x and y accessor functions), which would be something like this:

while (++i < n) {
  if ((value = d[i]) == null) {
    segments.push(points);
    points = [];
  } else {
    points.push([
      x.call(self, value, i),
      y.call(self, value, i)
    ]);
  }
}

But I'm not too sure about this approach either. There are different ways that you could define your data that would favor one approach or the other. I guess I need more examples of how missing data appears in practice to decide which is best. How is the data defined in your case?

@kitmonisit

I think your approach would result in cleaner and more generalizable d3 code. My data does not have nulls defined explicitly; instead I'll detect nulls based on the existing data (e.g. inject weekends on stock data, with null price). Anyway, it wouldn't hurt to convert my data input to segments, as you propose.

I did some experiments with d3.split, and it tends to produce a lot of paths, especially when viewing a wider data range. In my experience, the Chrome Inspector crashes when I try to check the markup (I drew strokes and fills, so the number of <path>s is twice the number of contiguous segments).

My proposed feature now looks quite easy to do, but only with the line generator. I mentioned in my commit comments that the area generator breaks down with my code. Because area depends on line, and areas are made using closed <path>s... Hmmm... No other way I could think of, except generate multiple <path>s for non-contiguous areas.

Or, draw the contiguous areas with one <path> and a complicated set of M and L commands. I could wrap my head around this idea if d3.svg.area.y0() baseline function returns a constant. But what if it varies with x0? That's yet another challenge to generalize the code.

@mbostock
Owner
mbostock commented Mar 8, 2012

It's possible to generate segmented areas as a single path; they'll look like: move-to top-left corner of area, line-to to produce topline of area, vertical line down to baseline, line-to in reverse order for baseline of area, close-path, move-to next top-left corner, repeat as necessary. If I have some time I'll give it a shot.

@kitmonisit

Yes, I thought so, too. What caught me was: if the x-coordinate at the right end of the baseline does not coincide with the x-coordinate at the right end of the topline? This might result in a slanted edge. Same goes for the left edge. If the baseline varies with x, we need a way to split it up to maintain straight vertical edges. Anyway, it's looking good. We have a way forward :)

@mbostock
Owner
mbostock commented Mar 8, 2012

Fortunately that's not possible if the area is defined by x, y0 and y1. It is possible to define x0 and x1, so it's a bit more complicated than that, but at any rate it should be identical to drawing an area with multiple path elements.

@mbostock mbostock referenced this issue Mar 17, 2012
Closed

Line defined #594

@mbostock
Owner

Fixed by #594. Take a look, and let me know what you think!

@mbostock mbostock closed this Mar 17, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.