Skip to content

Instantly share code, notes, and snippets.

@phoebebright
Created November 19, 2012 09:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phoebebright/4109792 to your computer and use it in GitHub Desktop.
Save phoebebright/4109792 to your computer and use it in GitHub Desktop.
sizing ladder
{"description":"sizing ladder","endpoint":"","display":"svg","public":true,"require":[],"tab":"edit","display_percent":0.5589295329452342,"play":false,"loop":false,"restart":false,"autoinit":true,"pause":true,"loop_type":"period","bv":false,"nclones":15,"clone_opacity":0.4,"duration":3000,"ease":"linear","dt":0.01}
// Node-link tree diagram using the Reingold-Tilford "tidy" algorithm
d3.layout.draw = function() {
var hierarchy = d3.layout.hierarchy().sort(null).value(null),
separation = d3_layout_treeSeparation,
size = [1, 1]; // width, height
function tree(d, i) {
var nodes = hierarchy.call(this, d, i),
root = nodes[0];
function firstWalk(node, previousSibling) {
var children = node.children,
layout = node._tree;
if (children && (n = children.length)) {
var n,
firstChild = children[0],
previousChild,
ancestor = firstChild,
child,
i = -1;
while (++i < n) {
child = children[i];
firstWalk(child, previousChild);
ancestor = apportion(child, previousChild, ancestor);
previousChild = child;
}
d3_layout_treeShift(node);
var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim);
if (previousSibling) {
layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
layout.mod = layout.prelim - midpoint;
} else {
layout.prelim = midpoint;
}
} else {
if (previousSibling) {
layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
}
}
}
function secondWalk(node, x) {
node.x = node._tree.prelim + x;
var children = node.children;
if (children && (n = children.length)) {
var i = -1,
n;
x += node._tree.mod;
while (++i < n) {
secondWalk(children[i], x);
}
}
}
function apportion(node, previousSibling, ancestor) {
if (previousSibling) {
var vip = node,
vop = node,
vim = previousSibling,
vom = node.parent.children[0],
sip = vip._tree.mod,
sop = vop._tree.mod,
sim = vim._tree.mod,
som = vom._tree.mod,
shift;
while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
vom = d3_layout_treeLeft(vom);
vop = d3_layout_treeRight(vop);
vop._tree.ancestor = node;
shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip);
if (shift > 0) {
d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift);
sip += shift;
sop += shift;
}
sim += vim._tree.mod;
sip += vip._tree.mod;
som += vom._tree.mod;
sop += vop._tree.mod;
}
if (vim && !d3_layout_treeRight(vop)) {
vop._tree.thread = vim;
vop._tree.mod += sim - sop;
}
if (vip && !d3_layout_treeLeft(vom)) {
vom._tree.thread = vip;
vom._tree.mod += sip - som;
ancestor = node;
}
}
return ancestor;
}
// Initialize temporary layout variables.
d3_layout_treeVisitAfter(root, function(node, previousSibling) {
node._tree = {
ancestor: node,
prelim: 0,
mod: 0,
change: 0,
shift: 0,
number: previousSibling ? previousSibling._tree.number + 1 : 0
};
});
// Compute the layout using Buchheim et al.'s algorithm.
firstWalk(root);
secondWalk(root, -root._tree.prelim);
// Compute the left-most, right-most, and depth-most nodes for extents.
var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost),
right = d3_layout_treeSearch(root, d3_layout_treeRightmost),
deep = d3_layout_treeSearch(root, d3_layout_treeDeepest),
x0 = left.x - separation(left, right) / 2,
x1 = right.x + separation(right, left) / 2,
y1 = deep.depth || 1;
// Clear temporary layout variables; transform x and y.
d3_layout_treeVisitAfter(root, function(node) {
node.x = (node.x - x0) / (x1 - x0) * size[0];
node.y = node.depth / y1 * size[1];
delete node._tree;
});
return nodes;
}
tree.separation = function(x) {
if (!arguments.length) return separation;
separation = x;
return tree;
};
tree.size = function(x) {
if (!arguments.length) return size;
size = x;
return tree;
};
return d3_layout_hierarchyRebind(tree, hierarchy);
};
function d3_layout_treeSeparation(a, b) {
return a.parent == b.parent ? 1 : 2;
}
// function d3_layout_treeSeparationRadial(a, b) {
// return (a.parent == b.parent ? 1 : 2) / a.depth;
// }
function d3_layout_treeLeft(node) {
var children = node.children;
return children && children.length ? children[0] : node._tree.thread;
}
function d3_layout_treeRight(node) {
var children = node.children,
n;
return children && (n = children.length) ? children[n - 1] : node._tree.thread;
}
function d3_layout_treeSearch(node, compare) {
var children = node.children;
if (children && (n = children.length)) {
var child,
n,
i = -1;
while (++i < n) {
if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) {
node = child;
}
}
}
return node;
}
function d3_layout_treeRightmost(a, b) {
return a.x - b.x;
}
function d3_layout_treeLeftmost(a, b) {
return b.x - a.x;
}
function d3_layout_treeDeepest(a, b) {
return a.depth - b.depth;
}
function d3_layout_treeVisitAfter(node, callback) {
function visit(node, previousSibling) {
var children = node.children;
if (children && (n = children.length)) {
var child,
previousChild = null,
i = -1,
n;
while (++i < n) {
child = children[i];
visit(child, previousChild);
previousChild = child;
}
}
callback(node, previousSibling);
}
visit(node, null);
}
function d3_layout_treeShift(node) {
var shift = 0,
change = 0,
children = node.children,
i = children.length,
child;
while (--i >= 0) {
child = children[i]._tree;
child.prelim += shift;
child.mod += shift;
shift += child.shift + (change += child.change);
}
}
function d3_layout_treeMove(ancestor, node, shift) {
ancestor = ancestor._tree;
node = node._tree;
var change = shift / (node.number - ancestor.number);
ancestor.change += change;
node.change -= change;
node.shift += shift;
node.prelim += shift;
node.mod += shift;
}
function d3_layout_treeAncestor(vim, node, ancestor) {
return vim._tree.ancestor.parent == node.parent
? vim._tree.ancestor
: ancestor;
}
// A method assignment helper for hierarchy subclasses.
function d3_layout_hierarchyRebind(object, hierarchy) {
d3.rebind(object, hierarchy, "sort", "children", "value");
// Add an alias for links, for convenience.
object.links = d3_layout_hierarchyLinks;
// If the new API is used, enabling inlining.
object.nodes = function(d) {
d3_layout_hierarchyInline = true;
return (object.nodes = object)(d);
};
return object;
}
// Returns an array source+target objects for the specified nodes.
function d3_layout_hierarchyLinks(nodes) {
return d3.merge(nodes.map(function(parent) {
return (parent.children || []).map(function(child) {
return {source: parent, target: child};
});
}));
}
//http://bl.ocks.org/999346
// In a doubles match Player 1 and Player 3 play against Player 2 and Player 4
//http://bl.ocks.org/999346
//http://bl.ocks.org/999346
var svg = d3.select("svg");
/*
var data = treeify(createDraw(8));
console.log(JSON.stringify(data));
*/
var data = {
"id": "m4_1",
"round": 4,
"match": 1,
match_type: "normal",
"notes": "Monday 18:30",
"result": "6/4, 6/2, 2/6, 6/4",
"player1": "Rob Fahy",
"player2": "Bryn Sayers",
"winner": "Rob Fahy",
"children": [
{
"id": "m3_1",
"round": 3,
"match": 1,
"notes": "Saturday 14:00",
"result": "6/0, 6/1. 6/0",
"player1": "Rob Fahy",
"player2": "Camden Riviere",
"winner": "Rob Fahy",
"next_round": "m4_1",
"children": [
{
"id": "m2_1",
"round": 2,
"match": 1,
"player1": "Rob Fahy",
"winner": "Rob Fahy",
"player2": "Ben Mathews",
"next_round": "m3_1",
"children": [
]
},
{
"id": "m2_2",
"round": 2,
"match": 2,
"player1": "",
"player2": "",
"next_round": "m3_1",
"children": [
{
"id": "m1_3",
"round": 1,
"match": 3,
"player1": "",
"player2": "",
"next_round": "m2_2"
},
{
"id": "m1_4",
"round": 1,
"match": 4,
"player1": "",
"player2": "",
"next_round": "m2_2"
}
]
}
]
},
{
"id": "m3_2",
"round": 3,
"match": 2,
"player1": "",
"player2": "",
"next_round": "m4_1",
"children": [
{
"id": "m2_3",
"round": 2,
"match": 3,
"player1": "",
"player2": "",
"next_round": "m3_2",
"children": [
{
"id": "m1_5",
"round": 1,
"match": 5,
"player1": "",
"player2": "",
"next_round": "m2_3"
},
{
"id": "m1_6",
"round": 1,
"match": 6,
"player1": "",
"player2": "",
"next_round": "m2_3"
}
]
},
{
"id": "m2_4",
"round": 2,
"match": 4,
"player1": "",
"player2": "",
"next_round": "m3_2",
"children": [
{
"id": "m1_7",
"round": 1,
"match": 7,
"player1": "",
"player2": "",
"next_round": "m2_4"
},
{
"id": "m1_8",
"round": 1,
"match": 8,
"player1": "",
"player2": "",
"next_round": "m2_4"
}
]
}
]
}
]
};
var margin = {top: 20, right: 54, bottom: 30, left: 40},
width = 405 - margin.left - margin.right,
height = 367 - margin.top - margin.bottom;
var svg = d3.select("svg").attr("fill","pink");
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var draw = Ladder()
.data(data)
.width(500)
.height(504);
draw(g);
function Ladder() {
var setup = {"num_matches_round1": 4};
var col = 120,
rect_height = 40,
rect_width = 100;
var data = {};
var width = 600;
var height = 600;
var chart = function(g) {
var tree = d3.layout.draw()
/////.separation(function(a, b) { return a.parent === b.parent ? .75 : .8; })
.size([height, width]);
var nodes = tree.nodes(data);
var link = g.selectAll(".link")
.data(tree.links(nodes))
.enter().append("path")
.attr("class", "link")
.attr("d", elbow);
var node = g.selectAll(".match")
.data(nodes)
.enter().append("g")
.attr("class", "match")
.attr("transform", function(d) { return "translate(" + (width-d.y) + "," + d.x + ")"; });
node.append("rect")
.attr("class", "matchbox")
.attr("x", 16)
.attr("y", -26)
.attr("width", 173)
.attr("height", 38);
node.append("text")
.attr("class", "players")
.attr("x", 14)
.attr("y", -12)
.text(function(d) { return d.player1; });
node.append("text")
.attr("class", "players")
.attr("x", 14)
.attr("y", 18)
.text(function(d) { return d.player2; });
node.append("text")
.attr("class", "notes")
.attr("x", 14)
.attr("y", 3)
.text(function(d) {
return (d.result) ? d.result: d.notes;
});
// if there is a winner, then display
if (nodes[0].winner) {
var winner = g.append("g")
.data([nodes[0]])
.attr("id", "winner")
.attr("class", "winner")
.attr("transform", function(d) { return "translate(" + (width-d.y) + "," + d.x + ")"; });
winner.append("rect")
.attr("x", 124)
.attr("y", -135)
.attr("height", 75)
.attr("width", 154)
;
winner.append("text")
.attr("x", 137)
.attr("y", -93)
.text(function(d) {
return d.winner; });
}
/*
node.append("text")
.attr("x", 8)
.attr("y", 8)
.attr("dy", ".71em")
.attr("class", "about lifespan")
.text(function(d) { return d.born + "–" + d.died; });
node.append("text")
.attr("x", 8)
.attr("y", 8)
.attr("dy", "1.86em")
.attr("class", "about location")
.text(function(d) { return d.location; });
*/
function elbow(d, i) {
return "M" + (width - d.target.y +198) + "," + d.target.x
+ "H" +(width - d.source.y ) + "V" + d.source.x
+ "h10" ;
}
}
chart.data = function(value) {
if(!arguments.length) return data;
data = value;
return chart;
}
chart.width = function(value) {
if(!arguments.length) return width;
width = value;
return chart;
}
chart.height = function(value) {
if(!arguments.length) return height;
height = value;
return chart;
}
return chart;
}
// whole match is a bye
function byeMatch(match_id) {
d3.select("#match_"+match_id)
.attr("opacity", 0);
}
function createDraw(no_players, round) {
if (typeof round === "undefined") {
round = 1;
}
var no_matches = Math.ceil(no_players/2);
var matches = new Array();
for (n=0;n<no_matches;n++) {
matches[n] = {"id": "m"+round+"_"+(n+1), "round": round, "match": (n+1), "player1": "", "player2": ""};
// if not final round, link to match in the next round
if (no_matches > 1) {
matches[n]["next_round"] = "m"+(round+1)+"_"+Math.ceil((n+1)/2);
}
}
if (no_matches > 1) {
return matches.concat(createDraw(no_matches, (round+1)));
} else {
return matches;
}
}
function treeify(nodes) {
// http://stackoverflow.com/questions/11934862/d3-use-nest-function-to-turn-flat-data-with-parent-key-into-a-hierarchy
var nodeById = {};
// Index the nodes by id, in case they come out of order.
nodes.forEach(function(d) {
nodeById[d.id] = d;
});
// Lazily compute children.
nodes.forEach(function(d) {
if ("next_round" in d) {
var next = nodeById[d.next_round];
if (next.children) next.children.push(d);
else next.children = [d];
}
});
// root node is now final match so just return that
return nodes[nodes.length-1];
}
function flatten(tree) {
g.append("text")
.text(JSON.stringify(tree));
}
function get_id(id) {
// return part of string after underscore
// eg. year_2000 returns 2000
// only really need to do this where id is a number as if you use
// a number for an id it works in some functions and not others
return id.substr(id.indexOf("_")+1);
}
.matchbox {
fill: #323566;
stroke: #070713;
stroke-width: 1px;
opacity: 0.2;
}
.players {
stroke: black;
stroke-width: 0.648;
font-size: 12px;
}
.notes {
font-size: 11px;
stroke-width: 0.42;
stroke: red;
}
text {
font-family: "Helvetica Neue", Helvetica, sans-serif;
}
.link {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.winner {
font-size: 14px;
stroke-width: 0.588;
fill: red;
}
.winner text{
font-size: 14px;
stroke-width: 1;
stroke: blue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment