d3 / d3-shape

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

add optional angle radius parameter in curbeBezier `curveStep` curves #209

Open roipoussiere opened 12 months ago

roipoussiere commented 12 months ago

Following #177, as suggested in https://github.com/d3/d3-shape/pull/177#issuecomment-809936541, it could be useful to have a curve similar to curveStep but with smooth angles:

image

An important note is that the curve angle should be identical whatever its length. This way we don't see curves duplication on nodes with many child of various link length:

image

Those curves could be named curbeBezierX and curbeBezierY as suggested in this example.

In fact, I think those curves could be included in curveStep, by adding an extra optional parameter allowing to define the angle radius, which is 0 by default.

This will also allow some customization. This is for instance large angle (ie curveStep(15)) and small angle (ie curveStep(5)) stepCurves, respectively:

image image

Some other visuals to get the idea:

curveStep(10)

image image

curveStepBefore(10)

image image

curveStepAfter(10)

image image

Edit: as pointed out by @Fil, the curve horizontal and vertical distance might be lower than twice the angle radius. In that case the radius remains the same, but angles stops before 90° in order to share the same tangential line. Example with several curves formed with curveStep(20), tangential line in red:

image

Fil commented 12 months ago

Nice! Note that you need the difference in x and the difference in y to be greater than twice the radius—the spec should also describe what to do when it's smaller.

roipoussiere commented 12 months ago

Good point! I edited the issue.

Fil commented 12 months ago

We'll need to specify all the cases :)

pseudo code:

move to x1,y1
xa = (x1+x2)/2; // x1+r if stepBefore, x2-r if stepAfter
ya = (y1+y2)/2;
sx = Math.sign(x2-x1); 
sy = Math.sign(y2-y1); 
if (|x2-x1| > 2r && |y2-y1| > 2r):
  line to xa - sx * r, y1 
  arc to xa, y1 + sy * r
  line to xa, y2 - sy * r
  arc to xa + sx * r, y2
  line to x2, y2
else if (|x2-x1| > 2r)
  … // line + bezier + line?
else if (|y2-y1| > 2r)
  … // ?? smaller r?
else
  ... // bezier ??
roipoussiere commented 12 months ago

Does that image cover all the cases?

image