Skip to content

Commit

Permalink
curve option for linkHorizontal and linkVertical (#177)
Browse files Browse the repository at this point in the history
* curve option for linkHorizontal and linkVertical

* if we're doing this, let's use d3.line

* d3.link(curve) instead of link.curve(curve) (#190)

* d3.link(curve) instead of link.curve(curve)

* invoke curve directly

* link.js

* add link tests

* more link tests

Co-authored-by: Mike Bostock <mbostock@gmail.com>
  • Loading branch information
Fil and mbostock authored Jan 7, 2022
1 parent bfbfba5 commit 3999c56
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 226 deletions.
288 changes: 147 additions & 141 deletions README.md

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions src/curve/bump.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pointRadial from "../pointRadial.js";

class Bump {
constructor(context, x) {
this._context = context;
Expand Down Expand Up @@ -36,10 +38,37 @@ class Bump {
}
}

class BumpRadial {
constructor(context) {
this._context = context;
}
lineStart() {
this._point = 0;
}
lineEnd() {}
point(x, y) {
x = +x, y = +y;
if (this._point++ === 0) {
this._x0 = x, this._y0 = y;
} else {
const p0 = pointRadial(this._x0, this._y0);
const p1 = pointRadial(this._x0, this._y0 = (this._y0 + y) / 2);
const p2 = pointRadial(x, this._y0);
const p3 = pointRadial(x, y);
this._context.moveTo(...p0);
this._context.bezierCurveTo(...p1, ...p2, ...p3);
}
}
}

export function bumpX(context) {
return new Bump(context, true);
}

export function bumpY(context) {
return new Bump(context, false);
}

export function bumpRadial(context) {
return new BumpRadial(context);
}
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export {default as pie} from "./pie.js";
export {default as areaRadial, default as radialArea} from "./areaRadial.js"; // Note: radialArea is deprecated!
export {default as lineRadial, default as radialLine} from "./lineRadial.js"; // Note: radialLine is deprecated!
export {default as pointRadial} from "./pointRadial.js";
export {linkHorizontal, linkVertical, linkRadial} from "./link/index.js";
export {link, linkHorizontal, linkVertical, linkRadial} from "./link.js";

export {default as symbol, symbolsStroke, symbolsFill, symbolsFill as symbols} from "./symbol.js";
export {default as symbolAsterisk} from "./symbol/asterisk.js";
Expand Down
72 changes: 72 additions & 0 deletions src/link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {path} from "d3-path";
import {slice} from "./array.js";
import constant from "./constant.js";
import {bumpX, bumpY, bumpRadial} from "./curve/bump.js";
import {x as pointX, y as pointY} from "./point.js";

function linkSource(d) {
return d.source;
}

function linkTarget(d) {
return d.target;
}

export function link(curve) {
let source = linkSource;
let target = linkTarget;
let x = pointX;
let y = pointY;
let context = null;
let output = null;

function link() {
let buffer;
const argv = slice.call(arguments);
const s = source.apply(this, argv);
const t = target.apply(this, argv);
if (context == null) output = curve(buffer = path());
output.lineStart();
argv[0] = s, output.point(+x.apply(this, argv), +y.apply(this, argv));
argv[0] = t, output.point(+x.apply(this, argv), +y.apply(this, argv));
output.lineEnd();
if (buffer) return output = null, buffer + "" || null;
}

link.source = function(_) {
return arguments.length ? (source = _, link) : source;
};

link.target = function(_) {
return arguments.length ? (target = _, link) : target;
};

link.x = function(_) {
return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), link) : x;
};

link.y = function(_) {
return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), link) : y;
};

link.context = function(_) {
return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), link) : context;
};

return link;
}

export function linkHorizontal() {
return link(bumpX);
}

export function linkVertical() {
return link(bumpY);
}

export function linkRadial() {
const l = link(bumpRadial);
l.angle = l.x, delete l.x;
l.radius = l.y, delete l.y;
return l;
}
84 changes: 0 additions & 84 deletions src/link/index.js

This file was deleted.

114 changes: 114 additions & 0 deletions test/link-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import assert from "assert";
import {path} from "d3-path";
import {link, linkHorizontal, linkVertical} from "../src/index.js";
import {curveLinear, curveBumpX, curveBumpY} from "../src/index.js";
import {assertPathEqual} from "./asserts.js";

it("link(curve) returns a default link with the given curve", () => {
const l = link(curveLinear);
assert.strictEqual(l.source()({source: 42}), 42);
assert.strictEqual(l.target()({target: 34}), 34);
assert.strictEqual(l.x()([42, 34]), 42);
assert.strictEqual(l.y()([42, 34]), 34);
assert.strictEqual(l.context(), null);
assertPathEqual(l({source: [0, 1], target: [2, 3]}), "M0,1L2,3");
});

it("link.source(source) sets source", () => {
const l = link(curveLinear);
const x = d => d.x;
assert.strictEqual(l.source(x), l);
assert.strictEqual(l.source(), x);
assertPathEqual(l({x: [0, 1], target: [2, 3]}), "M0,1L2,3");
});

it("link.target(target) sets target", () => {
const l = link(curveLinear);
const x = d => d.x;
assert.strictEqual(l.target(x), l);
assert.strictEqual(l.target(), x);
assertPathEqual(l({source: [0, 1], x: [2, 3]}), "M0,1L2,3");
});

it("link.source(f)(..args) passes arguments to the specified function f", () => {
const source = {name: "source"};
const target = {name: "target"};
const data = {source, target};
const extra = {name: "extra"};
const actual = [];
link(curveLinear).source(function(d) { actual.push([].slice.call(arguments)); return d; })(data, extra);
assert.deepStrictEqual(actual, [[data, extra]]);
});

it("link.target(f)(..args) passes source and arguments to the specified function f", () => {
const source = {name: "source"};
const target = {name: "target"};
const data = {source, target};
const extra = {name: "extra"};
const actual = [];
link(curveLinear).target(function(d) { actual.push([].slice.call(arguments)); return d; })(data, extra);
assert.deepStrictEqual(actual, [[data, extra]]);
});

it("link.x(x) sets x", () => {
const l = link(curveLinear);
const x = d => d.x;
assert.strictEqual(l.x(x), l);
assert.strictEqual(l.x(), x);
assertPathEqual(l({source: {x: 0, 1: 1}, target: {x: 2, 1: 3}}), "M0,1L2,3");
});

it("link.y(y) sets y", () => {
const l = link(curveLinear);
const y = d => d.y;
assert.strictEqual(l.y(y), l);
assert.strictEqual(l.y(), y);
assertPathEqual(l({source: {0: 0, y: 1}, target: {0: 2, y: 3}}), "M0,1L2,3");
});

it("link.x(f)(..args) passes source and arguments to the specified function f", () => {
const source = {name: "source"};
const target = {name: "target"};
const data = {source, target};
const extra = {name: "extra"};
const actual = [];
link(curveLinear).x(function() { actual.push([].slice.call(arguments)); })(data, extra);
assert.deepStrictEqual(actual, [[source, extra], [target, extra]]);
});

it("link.y(f)(..args) passes source and arguments to the specified function f", () => {
const source = {name: "source"};
const target = {name: "target"};
const data = {source, target};
const extra = {name: "extra"};
const actual = [];
link(curveLinear).y(function() { actual.push([].slice.call(arguments)); })(data, extra);
assert.deepStrictEqual(actual, [[source, extra], [target, extra]]);
});

it("linkHorizontal() is an alias for link(curveBumpX)", () => {
const l = linkHorizontal(), l2 = link(curveBumpX);
assert.strictEqual(l.source(), l2.source());
assert.strictEqual(l.target(), l2.target());
assert.strictEqual(l.x(), l2.x());
assert.strictEqual(l.y(), l2.y());
assert.strictEqual(l.context(), l2.context());
assertPathEqual(l({source: [0, 1], target: [2, 3]}), l2({source: [0, 1], target: [2, 3]}));
});

it("linkVertical() is an alias for link(curveBumpY)", () => {
const l = linkVertical(), l2 = link(curveBumpY);
assert.strictEqual(l.source(), l2.source());
assert.strictEqual(l.target(), l2.target());
assert.strictEqual(l.x(), l2.x());
assert.strictEqual(l.y(), l2.y());
assert.strictEqual(l.context(), l2.context());
assertPathEqual(l({source: [0, 1], target: [2, 3]}), l2({source: [0, 1], target: [2, 3]}));
});

it("link.context(context) sets the context", () => {
const p = path();
const l = link(curveLinear).context(p);
assert.strictEqual(l({source: [0, 1], target: [2, 3]}), undefined);
assertPathEqual(p, "M0,1L2,3");
});

0 comments on commit 3999c56

Please sign in to comment.