Skip to content

Commit

Permalink
Add d3.svg.diagonal.
Browse files Browse the repository at this point in the history
This is a utility for creating a Bézier curve between opposite corners of a
rectangle. This is commonly used to draw smooth curves connecting parent and
child nodes in a hierarchical node-link diagram. A projection may be specified
which allows the curve to be transformed from polar coordinates.

This commit also changes the semantics of the recently-added `links` method,
such that the objects have `source` and `target` properties that match the
default diagonal format.
  • Loading branch information
mbostock committed Apr 14, 2011
1 parent 8d7bd91 commit 126e625
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 71 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ d3.svg.js: \
src/svg/line.js \
src/svg/area.js \
src/svg/chord.js \
src/svg/diagonal.js \
src/svg/mouse.js \
src/svg/symbol.js

Expand Down
38 changes: 38 additions & 0 deletions d3.js
Original file line number Diff line number Diff line change
Expand Up @@ -2870,6 +2870,44 @@ function d3_svg_chordStartAngle(d) {
function d3_svg_chordEndAngle(d) {
return d.endAngle;
}
d3.svg.diagonal = function() {
var source = d3_svg_chordSource,
target = d3_svg_chordTarget,
projection = d3_svg_diagonalProjection;

function diagonal(d, i) {
var p0 = source.call(this, d, i),
p3 = target.call(this, d, i),
m = (p0.y + p3.y) / 2,
p = [p0, {x: p0.x, y: m}, {x: p3.x, y: m}, p3];
p = p.map(projection);
return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
}

diagonal.source = function(x) {
if (!arguments.length) return source;
source = d3_functor(x);
return diagonal;
};

diagonal.target = function(x) {
if (!arguments.length) return target;
target = d3_functor(x);
return diagonal;
};

diagonal.projection = function(x) {
if (!arguments.length) return projection;
projection = x;
return diagonal;
};

return diagonal;
};

function d3_svg_diagonalProjection(d) {
return [d.x, d.y];
}
d3.svg.mouse = function(container) {
var point = (container.ownerSVGElement || container).createSVGPoint();
if ((d3_mouse_bug44083 < 0) && (window.scrollX || window.scrollY)) {
Expand Down
4 changes: 2 additions & 2 deletions d3.layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -968,11 +968,11 @@ d3.layout.tree = function() {
return tree;
};

// Returns an array parent+child objects for the specified nodes.
// Returns an array source+target objects for the specified nodes.
function d3_layout_treeLinks(nodes) {
return d3.merge(nodes.map(function(parent) {
return (parent.children || []).map(function(child) {
return {parent: parent, child: child};
return {source: parent, target: child};
});
}));
}
Expand Down
2 changes: 1 addition & 1 deletion d3.layout.min.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions d3.min.js

Large diffs are not rendered by default.

25 changes: 7 additions & 18 deletions examples/cluster/cluster-radial.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ var cluster = d3.layout.cluster()
.sort(null)
.children(function(d) { return isNaN(d.value) ? d3.entries(d.value) : null; });

var diagonal = d3.svg.diagonal()
.projection(function(d) {
var r = d.y, a = (d.x - 90) / 180 * Math.PI;
return [r * Math.cos(a), r * Math.sin(a)];
});

var vis = d3.select("#chart").append("svg:svg")
.attr("width", r * 2)
.attr("height", r * 2)
Expand All @@ -18,7 +24,7 @@ d3.json("flare.json", function(json) {
.data(cluster.links(nodes))
.enter().append("svg:path")
.attr("class", "link")
.attr("d", path);
.attr("d", diagonal);

var node = vis.selectAll("g.node")
.data(nodes)
Expand All @@ -35,21 +41,4 @@ d3.json("flare.json", function(json) {
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) { return d.data.key; });

// Computes a pretty Bézier curve from parent to child. TODO reusable helper?
function path(d) {
var y0 = (d.parent.y + d.child.y) / 2,
p0 = d.parent,
p3 = d.child,
p1 = {x: p0.x, y: y0},
p2 = {x: p3.x, y: y0};
return "M" + x(p0) + "," + y(p0)
+ "C" + x(p1) + "," + y(p1)
+ " " + x(p2) + "," + y(p2)
+ " " + x(p3) + "," + y(p3);
}

// Radial scales for x and y.
function x(d) { return d.y * Math.cos((d.x - 90) / 180 * Math.PI); }
function y(d) { return d.y * Math.sin((d.x - 90) / 180 * Math.PI); }
});
18 changes: 4 additions & 14 deletions examples/cluster/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ var cluster = d3.layout.cluster()
.sort(null)
.children(function(d) { return isNaN(d.value) ? d3.entries(d.value) : null; });

var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });

var vis = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h)
Expand All @@ -19,7 +22,7 @@ d3.json("flare.json", function(json) {
.data(cluster.links(nodes))
.enter().append("svg:path")
.attr("class", "link")
.attr("d", path);
.attr("d", diagonal);

var node = vis.selectAll("g.node")
.data(nodes)
Expand All @@ -35,17 +38,4 @@ d3.json("flare.json", function(json) {
.attr("dy", 3)
.attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.data.key; });

// Computes a pretty Bézier curve from parent to child. TODO reusable helper?
function path(d) {
var y = (d.parent.y + d.child.y) / 2,
p0 = d.parent,
p3 = d.child,
p1 = {x: p0.x, y: y},
p2 = {x: p3.x, y: y};
return "M" + p0.y + "," + p0.x
+ "C" + p1.y + "," + p1.x
+ " " + p2.y + "," + p2.x
+ " " + p3.y + "," + p3.x;
}
});
25 changes: 7 additions & 18 deletions examples/tree/tree-radial.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ var tree = d3.layout.tree()
.children(function(d) { return isNaN(d.value) ? d3.entries(d.value) : null; })
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });

var diagonal = d3.svg.diagonal()
.projection(function(d) {
var r = d.y, a = (d.x - 90) / 180 * Math.PI;
return [r * Math.cos(a), r * Math.sin(a)];
});

var vis = d3.select("#chart").append("svg:svg")
.attr("width", r * 2)
.attr("height", r * 2 - 150)
Expand All @@ -19,7 +25,7 @@ d3.json("flare.json", function(json) {
.data(tree.links(nodes))
.enter().append("svg:path")
.attr("class", "link")
.attr("d", path);
.attr("d", diagonal);

var node = vis.selectAll("g.node")
.data(nodes)
Expand All @@ -36,21 +42,4 @@ d3.json("flare.json", function(json) {
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) { return d.data.key; });

// Computes a pretty Bézier curve from parent to child. TODO reusable helper?
function path(d) {
var y0 = (d.parent.y + d.child.y) / 2,
p0 = d.parent,
p3 = d.child,
p1 = {x: p0.x, y: y0},
p2 = {x: p3.x, y: y0};
return "M" + x(p0) + "," + y(p0)
+ "C" + x(p1) + "," + y(p1)
+ " " + x(p2) + "," + y(p2)
+ " " + x(p3) + "," + y(p3);
}

// Radial scales for x and y.
function x(d) { return d.y * Math.cos((d.x - 90) / 180 * Math.PI); }
function y(d) { return d.y * Math.sin((d.x - 90) / 180 * Math.PI); }
});
18 changes: 4 additions & 14 deletions examples/tree/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ var tree = d3.layout.tree()
.sort(null)
.children(function(d) { return isNaN(d.value) ? d3.entries(d.value) : null; });

var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });

var vis = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h)
Expand All @@ -19,7 +22,7 @@ d3.json("flare.json", function(json) {
.data(tree.links(nodes))
.enter().append("svg:path")
.attr("class", "link")
.attr("d", path);
.attr("d", diagonal);

var node = vis.selectAll("g.node")
.data(nodes)
Expand All @@ -35,17 +38,4 @@ d3.json("flare.json", function(json) {
.attr("dy", 3)
.attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
.text(function(d) { return d.data.key; });

// Computes a pretty Bézier curve from parent to child. TODO reusable helper?
function path(d) {
var y = (d.parent.y + d.child.y) / 2,
p0 = d.parent,
p3 = d.child,
p1 = {x: p0.x, y: y},
p2 = {x: p3.x, y: y};
return "M" + p0.y + "," + p0.x
+ "C" + p1.y + "," + p1.x
+ " " + p2.y + "," + p2.x
+ " " + p3.y + "," + p3.x;
}
});
4 changes: 2 additions & 2 deletions src/layout/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ d3.layout.tree = function() {
return tree;
};

// Returns an array parent+child objects for the specified nodes.
// Returns an array source+target objects for the specified nodes.
function d3_layout_treeLinks(nodes) {
return d3.merge(nodes.map(function(parent) {
return (parent.children || []).map(function(child) {
return {parent: parent, child: child};
return {source: parent, target: child};
});
}));
}
Expand Down
38 changes: 38 additions & 0 deletions src/svg/diagonal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
d3.svg.diagonal = function() {
var source = d3_svg_chordSource,
target = d3_svg_chordTarget,
projection = d3_svg_diagonalProjection;

function diagonal(d, i) {
var p0 = source.call(this, d, i),
p3 = target.call(this, d, i),
m = (p0.y + p3.y) / 2,
p = [p0, {x: p0.x, y: m}, {x: p3.x, y: m}, p3];
p = p.map(projection);
return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
}

diagonal.source = function(x) {
if (!arguments.length) return source;
source = d3_functor(x);
return diagonal;
};

diagonal.target = function(x) {
if (!arguments.length) return target;
target = d3_functor(x);
return diagonal;
};

diagonal.projection = function(x) {
if (!arguments.length) return projection;
projection = x;
return diagonal;
};

return diagonal;
};

function d3_svg_diagonalProjection(d) {
return [d.x, d.y];
}

0 comments on commit 126e625

Please sign in to comment.