Skip to content

Commit

Permalink
Fix #1 - sibling padding for treemaps.
Browse files Browse the repository at this point in the history
I still need to implement asymmetric padding, and decide whether to retain the
old treemap.padding property now that there are two types of padding.
  • Loading branch information
mbostock committed Nov 12, 2015
1 parent a6ac009 commit 349334d
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 108 deletions.
27 changes: 14 additions & 13 deletions src/hierarchy.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,20 @@ export default function() {
return arguments.length ? (value = x, hierarchy) : value;
};

hierarchy.revalue = function(root) {
if (value) {
visitBefore(root, function(node) {
if (node.children) node.value = 0;
});
visitAfter(root, function(node) {
var parent;
if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
if (parent = node.parent) parent.value += node.value;
});
}
return root;
};
// TODO
// hierarchy.revalue = function(root) {
// if (value) {
// visitBefore(root, function(node) {
// if (node.children) node.value = 0;
// });
// visitAfter(root, function(node) {
// var parent;
// if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
// if (parent = node.parent) parent.value += node.value;
// });
// }
// return root;
// };

return hierarchy;
};
220 changes: 125 additions & 95 deletions src/treemap.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,91 @@
import hierarchy, {rebind} from "./hierarchy";
import {visitBefore} from "./visit";

var phi = (1 + Math.sqrt(5)) / 2;

var modes = {
"slice": 1,
"dice": 1,
"slice-dice": 1,
"squarify": 1
var modeByName = {
"slice": slice,
"dice": dice,
"slice-dice": sliceDice,
"squarify": squarify
};

function padNone(node) {
return node;
}

function padStandard(node, padding) {
var x = node.x + padding[3],
y = node.y + padding[0],
dx = node.dx - padding[1] - padding[3],
dy = node.dy - padding[0] - padding[2];
function pad(node, top, right, bottom, left) {
var x = node.x + left,
y = node.y + top,
dx = node.dx - right - left,
dy = node.dy - top - bottom;
if (dx < 0) x += dx / 2, dx = 0;
if (dy < 0) y += dy / 2, dy = 0;
return {x: x, y: y, dx: dx, dy: dy};
}

function nodeSlice(nodes, rect, value) {
var i0 = -1,
n = nodes.length,
// function padConstant(padding) {
// return function(node) {
// pad(node, padding[0], padding[1], padding[2], padding[3]);
// };
// }

// function padFunction(padding) {
// return function(node) {
// var p = padding.call(treemap, node, node.depth);
// if (p == null) return;
// if (Array.isArray(p)) pad(node, +p[0], +p[1], +p[2], +p[3]);
// else p = +p, pad(node, p, p, p, p);
// };
// }

function sliceDice(parent, rect) {
(parent.depth & 1 ? slice : dice)(parent, rect);
}

function slice(parent, rect) {
var nodes = parent.children,
node,
x = rect.x, y = rect.y,
dx = rect.dx, dy = rect.dy,
ky = dy / value;
i = -1,
n = nodes.length,
x = rect.x,
y = rect.y,
dx = rect.dx,
ky = rect.dy / parent.value;

while (++i0 < n) {
node = nodes[i0], node.x = x, node.y = y, node.dx = dx;
while (++i < n) {
node = nodes[i], node.x = x, node.y = y, node.dx = dx;
y += node.dy = node.value * ky;
}
}

function nodeDice(nodes, rect, value) {
var i0 = -1,
n = nodes.length,
function dice(parent, rect) {
var nodes = parent.children,
node,
x = rect.x, y = rect.y,
dx = rect.dx, dy = rect.dy,
kx = dx / value;
i = -1
n = nodes.length,
x = rect.x,
y = rect.y,
dx = rect.dx,
ky = rect.dy / parent.value;

while (++i0 < n) {
node = nodes[i0], node.x = x, node.y = y, node.dy = dy;
while (++i < n) {
node = nodes[i], node.x = x, node.y = y, node.dy = dy;
x += node.dx = node.value * kx;
}
}

function nodeRound(node) {
node.dx = Math.round(node.x + node.dx) - (node.x = Math.round(node.x));
node.dy = Math.round(node.y + node.dy) - (node.y = Math.round(node.y));
}

export default function() {
var layout = hierarchy(),
round = false,
size = [1, 1],
padding = null,
pad = padNone,
sticky = false,
stickies,
mode = "squarify",
ratio = phi;

function padFunction(node) {
var p = padding.call(treemap, node, node.depth);
return p == null
? padNone(node)
: padStandard(node, typeof p === "number" ? [p, p, p, p] : p);
}

function padConstant(node) {
return padStandard(node, padding);
}

function nodeSquarify(nodes, rect, value) {
var i0 = 0,
i1,
n = nodes.length,
function squarify(ratio) {
return function(parent, rect) {
var nodes = parent.children,
node,
nodeValue,
x = rect.x, y = rect.y,
dx = rect.dx, dy = rect.dy,
i0 = 0,
i1,
n = nodes.length,
x = rect.x,
y = rect.y,
dx = rect.dx,
dy = rect.dy,
cx, cy,
kx, ky,
value = parent.value,
sumValue,
minValue,
maxValue,
Expand Down Expand Up @@ -128,19 +126,26 @@ export default function() {

value -= sumValue;
}
}
};
}

function recurse(parent) {
var children = parent.children;
if (children) {
(mode === "slice" ? nodeSlice
: mode === "dice" ? nodeDice
: mode === "slice-dice" ? parent.depth & 1 ? nodeSlice : nodeDice
: nodeSquarify)(children, pad(parent), parent.value);
if (round) children.forEach(nodeRound);
children.forEach(recurse);
}
}
function applyRound(node) {
node.dx = Math.round(node.x + node.dx) - (node.x = Math.round(node.x));
node.dy = Math.round(node.y + node.dy) - (node.y = Math.round(node.y));
}

export default function() {
var layout = hierarchy(),
size = [1, 1],
parentPadding = 0,
siblingPadding = 0,
// applyPadding,
ratio = phi,
round = false,
// sticky = false,
// stickies,
modeName = "squarify",
applyMode = squarify(ratio);

// // Recursively resizes the specified node's children into existing rows.
// // Preserves the existing layout!
Expand All @@ -166,16 +171,22 @@ export default function() {
// }

function treemap(d) {
var nodes = stickies || layout(d),
var nodes = /* stickies ||*/ layout(d),
root = nodes[0];
root.x = 0;
root.y = 0;
root.dx = size[0];
root.dy = size[1];
if (stickies) layout.revalue(root);
root.dx = size[0] + siblingPadding;
root.dy = size[1] + siblingPadding;
visitBefore(root, function(node) {
if (round) applyRound(node);
if (node.children) applyMode(node, pad(node, parentPadding, parentPadding, parentPadding, parentPadding));
node.dx = Math.max(0, node.dx - siblingPadding);
node.dy = Math.max(0, node.dy - siblingPadding);
});
// if (stickies) layout.revalue(root);
// scale([root], root.dx * root.dy / root.value);
(stickies ? stickify : recurse)(root);
if (sticky) stickies = nodes;
// (stickies ? stickify : recurse)(root);
// if (sticky) stickies = nodes;
return nodes;
}

Expand All @@ -185,37 +196,56 @@ export default function() {
return treemap;
};

treemap.padding = function(x) {
if (!arguments.length) return Array.isArray(padding) ? padding.slice() : padding;
var t;
pad = x == null ? (padding = null, padNone)
: (t = typeof x) === "function" ? (padding = x, padFunction)
: t === "number" ? (padding = [x, x, x, x], padConstant)
: (padding = [+x[0], +x[1], +x[2], +x[3]], padConstant);
// TODO asymmetric padding
treemap.parentPadding = function(x) {
if (!arguments.length) return parentPadding;
parentPadding = +x;
return treemap;
};

// TODO asymmetric padding
treemap.siblingPadding = function(x) {
if (!arguments.length) return siblingPadding;
siblingPadding = +x;
return treemap;
};

// treemap.padding = function(x) {
// if (!arguments.length) return Array.isArray(padding) ? padding.slice() : padding;
// var t;
// applyPadding = x == null ? padding = null
// : (t = typeof x) === "function" ? padFunction(padding = x)
// : (Array.isArray(x)
// ? (padding = [+x[0], +x[1], +x[2], +x[3]])
// : (x = +x, padding = [x, x, x, x])
// , padConstant(padding));
// return treemap;
// };

treemap.round = function(x) {
if (!arguments.length) return round;
round = !!x;
return treemap;
};

treemap.sticky = function(x) {
if (!arguments.length) return sticky;
sticky = !!x, stickies = null;
return treemap;
};
// TODO
// treemap.sticky = function(x) {
// if (!arguments.length) return sticky;
// sticky = !!x, stickies = null;
// return treemap;
// };

treemap.ratio = function(x) {
if (!arguments.length) return ratio;
ratio = +x;
if (modeName === "squarify") applyMode = squarify(ratio);
return treemap;
};

treemap.mode = function(x) {
if (!arguments.length) return mode;
mode = modes.hasOwnProperty(x) ? x + "" : "squarify";
if (!arguments.length) return modeName;
modeName = modeByName.hasOwnProperty(x += "") ? x : "squarify";
applyMode = modeName === "squarify" ? squarify(ratio) : modeByName[modeName];
return treemap;
};

Expand Down

0 comments on commit 349334d

Please sign in to comment.