-
-
Save phoebebright/4109792 to your computer and use it in GitHub Desktop.
sizing ladder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"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} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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}; | |
}); | |
})); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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