d3 / d3-shape

Graphical primitives for visualization, such as lines and areas.
https://d3js.org/d3-shape
ISC License
2.48k stars 308 forks source link

curveHorizontal, curveVertical? #113

Open jeffsf opened 6 years ago

jeffsf commented 6 years ago

In working with discrete-time and discrete-level data, I find that the risers in the data can end up being visually deceptive, especially where there is a single-level change involved. Use of a collection of "dots" appears to be much slower to render than the path used by d3.line, to the point of making scrolling and zooming even on a desktop browser rough, and nearly impossible on a phone.

From a presentation standpoint, the dot is "fine" -- it is just the performance problems, especially as mobile monitoring of the time series is a primary use case.

With d3.curveStep: image

With drawing a dot: image

At least for me, the visual impression of how many samples are at the median vs. high/low over a segment of time is very different between the two representations.

(Using d3.line has similar challenges with the near-vertical connectors as d3.curveStep has with the risers.)

On a desktop, the dataset-size limit is around 1000-1500 dots before the re-draw times introduce so much lag as to make scroll/zoom frustrating. (There are three other d3.line data sets as well on the graph, but it is the dots that dramatically slow the re-draw time)

jeffsf commented 6 years ago

Creating a "custom" line type based on step.js resulted in the kind of display I was looking for (after adjusting line width and adding line caps) and a very significant improvement in draw time (from 50-100 ms down to 15-20 ms)

I'm not sure if/how you would want to incorporate this, as well as for anyone that finds this through search, the overridden point: I'm using is

  point: function(x, y) {
    x = +x, y = +y;
    switch (this._point) {
      case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
      case 1: this._point = 2; // proceed
      default: {
        if (this._t <= 0) {
          this._context.moveTo(this._x, y);
          this._context.lineTo(x, y);
        } else {
          var x1 = this._x * (1 - this._t) + x * this._t;
          this._context.lineTo(x1, this._y);
          this._context.moveTo(x1, y);
        }
        break;
      }
    }
    this._x = x, this._y = y;
  }

(only two changes; replacing lineTo with moveTo)

image

mbostock commented 4 years ago

I propose we call this “curveHorizontal”, and include a curveVertical for the vertically-oriented equivalent.

Fil commented 4 years ago

About the "collection of dots" there is also the curvePoints proposal https://github.com/d3/d3-shape/issues/154