Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
d3.js: Custom curve with rounded steps. Based on d3.curveStep.
/**
* Rounded step curve by Kevin Weber. Based on d3.curveStep.
*
* Configuration options:
* `distance`: Distance between two points that are connected by a bezier curve. Range: 0 to 1. Default: 0.5
* `shift`: Shift the middle of the bezier curve along the x-axis. The range of the shift depends on the distance. If distance equals 0, the shift ranges from 0 to 1. Default: 0.5
* `tilt`: Determines how steep the curve should be. 0 makes the bezier curve straight/diagonal while 1 makes it vertical in the middle. Range: 0 to 1. Default: 0.5
*/
export default class CustomCurve {
/**
* Constructor
*
* @param {object} context d3.js curve context
* @param {object} config Optional configuration
*/
constructor(context, config) {
this.context = context;
this.config = {
distance: 0.5, // Range: 0 to 1
shift: 0.5, // Range: (0 - distance) to (1 - distance)
tilt: 0.5, // Range: 0 to 1
...config,
};
this._t = this.config.shift;
}
areaStart() {
this._line = 0;
}
areaEnd() {
this._line = NaN;
}
lineStart() {
this._x = this._y = NaN;
this._point = 0;
}
lineEnd() {
if (0 < this._t && this._t < 1 && this._point === 2) this.context.lineTo(this._x, this._y);
if (this._line || (this._line !== 0 && this._point === 1)) this.context.closePath();
if (this._line >= 0) this._t = 1 - this._t, this._line = 1 - this._line;
}
point(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.lineTo(this._x, y);
this.context.lineTo(x, y);
} else {
const x1 = this._x * (1 - this._t) + x * this._t;
const pointOffset = (x - this._x) * (this.config.distance / 2);
const tilt = this.config.tilt;
const points = {
a: [x1 - pointOffset, this._y], // Point on left divider
// b: [x1, (this._y + (y - this._y) / 2)], // Point in between
c: [x1 + pointOffset, y], // Point on right divider
};
const yDistance = points.c[1] - points.a[1];
const xDistance = points.c[0] - points.a[0];
const controlPoints = {
ab1: [points.a[0] + xDistance * tilt, points.a[1]], // Control point starting from poin on left divider
// ab2: [points.b[0], points.b[1] - yDistance * 0.5], // First control point starting from point in between
// bc1: [points.b[0], points.b[1] + yDistance * 0.5], // Second control point starting from point in between
bc2: [points.c[0] - xDistance * tilt, points.c[1]], // Control point starting from poin on right divider
};
this.context.lineTo(...points.a);
this.context.bezierCurveTo(...controlPoints.ab1, ...controlPoints.bc2, ...points.c);
// NOTE: The following lines might be helpful in case we consider drawing two curves connected by the point in the middle (points.b)
// this.context.bezierCurveTo(...controlPoints.ab1, ...controlPoints.ab2, ...points.b);
// this.context.bezierCurveTo(...controlPoints.bc1, ...controlPoints.bc2, ...points.c);
}
break;
}
}
this._x = x, this._y = y;
}
}
import CurveStepRounded from './d3.curveStepRounded.js';
...
// Instead of `d3.curveStep` add this function:
function (context) {
return new CurveStepRounded(context, {
distance: 0.9,
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.