5

d3.line(data) returns a path data string. But, If I understand correctly, d3.line(data).context(context) draws directly the line on the canvas. How can I get the array of points used to draw the line on the canvas?

And, is it possible to change the preciseness of the line resulting from d3.line().curve() by passing the number of desired points?

Here is what I want to achieve:

makeACurvedLine([p1, p2, p3], 6)

And get the array of coordinates of the 6 points from the smoothed line.

What is the best way to achieve it?

Zamadhi Eturu
  • 131
  • 2
  • 10

2 Answers2

3

When you use d3.line().context(context)(data) with a canvas context, it uses canvas's built-in functions to draw arcs, curves, and other non-linear elements. This means d3 by itself doesn't really reduce your path to a bunch of points it connects with straight lines.

However, the browser itself has built-in functions to get points along an SVG path. One approach is to create an invisible svg path and use those functions to calculate path points. For the following snippet, I'll use uniformly spaced points, though you might want something more refined if there are some parts that are more curvy than others.

var svg = d3.select('body')
  .append('svg')
  .style('display', 'none');

var lineGenerator = d3.line().x(d=>d[0]).y(d=>d[1]).curve(d3.curveNatural);
var path = svg.append('path');

function getLinePoints(curvePoints, numPoints){
  path.attr('d', lineGenerator(curvePoints));
  var svgLine = path.node();
  var lineLength = svgLine.getTotalLength();
  var interval;
  if (numPoints === 1) {
    interval = 0;
  } else {
    interval = lineLength / (numPoints - 1);
  }
  return d3.range(numPoints).map(function (d) {
    var point = svgLine.getPointAtLength(d * interval);
    return [ point.x, point.y ];
  });
};


console.log(getLinePoints([[0,0],[0,10],[20,30]], 6));
<script src="https://d3js.org/d3.v5.min.js"></script>
Steve
  • 10,435
  • 15
  • 21
  • Thank you, `you might want something more refined if there are some parts that are more curvy than others` How could I achieve that? And would it be possible to obtain the same result without SVG, in full js? – Zamadhi Eturu Jul 30 '18 at 15:31
  • You could achieve a more refined selection of points by first using the above technique for a lot of points, then removing points via a line simplification algorithm until you reach the desired number of points. (here's a decent article about line simplification: https://bost.ocks.org/mike/simplify/). As for the same results without SVG, I'm not aware of another easy way to do it in vanilla JS, without relying on libraries. – Steve Jul 30 '18 at 16:54
  • I found a nice solution, without any library or SVG, from @epistemex here: https://stackoverflow.com/questions/7054272/how-to-draw-smooth-curve-through-n-points-using-javascript-html5-canvas – Zamadhi Eturu Jul 30 '18 at 17:09
  • It looks like that answer is for the opposite of what you're originally asking: it makes a curve from a bunch of points rather than vice versa. d3 also provides a variety of functions to do that too with curves. See: https://github.com/d3/d3-shape#curves – Steve Jul 30 '18 at 17:20
  • `function getCurvePoints(pts, tension, isClosed, numOfSegments)` from the link above makes an array of points smoothed from a bunch of points. – Zamadhi Eturu Jul 30 '18 at 17:24
  • I understand what you're saying now. It does look like a very nice solution, and looks like it could work, depending on whether your curves are cardinal splines. – Steve Jul 30 '18 at 17:34
0

You have to apply the line() yourself.

var x = d3.scaleLinear(), y = d3.scaleLinear();
var line = d3.line().x(d => x(d.input) ).y(d => y(d.value));
var points = data.map( e =>{ return {x:line.x()(e) , y:line.y()(e)}; };
rioV8
  • 24,506
  • 3
  • 32
  • 49
  • I could be missing something, correct me if I'm wrong, but won't this will miss any interpolated curve? As you are scaling the points in the dataset and not any intermediate values of the curve. – Andrew Reid Jul 30 '18 at 02:35
  • Yes, after seeing the other answer and reading the question more carefully I realized he wanted more then just the coordinates shown in the `path`. – rioV8 Jul 30 '18 at 02:56