source: Dev/trunk/d3/src/layout/treemap.js @ 76

Last change on this file since 76 was 76, checked in by fpvanagthoven, 14 years ago

d3

File size: 5.8 KB
Line 
1// Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk
2// Modified to support a target aspect ratio by Jeff Heer
3d3.layout.treemap = function() {
4  var hierarchy = d3.layout.hierarchy(),
5      round = Math.round,
6      size = [1, 1], // width, height
7      padding = null,
8      pad = d3_layout_treemapPadNull,
9      sticky = false,
10      stickies,
11      ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio
12
13  // Compute the area for each child based on value & scale.
14  function scale(children, k) {
15    var i = -1,
16        n = children.length,
17        child,
18        area;
19    while (++i < n) {
20      area = (child = children[i]).value * (k < 0 ? 0 : k);
21      child.area = isNaN(area) || area <= 0 ? 0 : area;
22    }
23  }
24
25  // Recursively arranges the specified node's children into squarified rows.
26  function squarify(node) {
27    if (!node.children) return;
28    var rect = pad(node),
29        row = [],
30        children = node.children.slice(), // copy-on-write
31        child,
32        best = Infinity, // the best row score so far
33        score, // the current row score
34        u = Math.min(rect.dx, rect.dy), // initial orientation
35        n;
36    scale(children, rect.dx * rect.dy / node.value);
37    row.area = 0;
38    while ((n = children.length) > 0) {
39      row.push(child = children[n - 1]);
40      row.area += child.area;
41      if ((score = worst(row, u)) <= best) { // continue with this orientation
42        children.pop();
43        best = score;
44      } else { // abort, and try a different orientation
45        row.area -= row.pop().area;
46        position(row, u, rect, false);
47        u = Math.min(rect.dx, rect.dy);
48        row.length = row.area = 0;
49        best = Infinity;
50      }
51    }
52    if (row.length) {
53      position(row, u, rect, true);
54      row.length = row.area = 0;
55    }
56    node.children.forEach(squarify);
57  }
58
59  // Recursively resizes the specified node's children into existing rows.
60  // Preserves the existing layout!
61  function stickify(node) {
62    if (!node.children) return;
63    var rect = pad(node),
64        children = node.children.slice(), // copy-on-write
65        child,
66        row = [];
67    scale(children, rect.dx * rect.dy / node.value);
68    row.area = 0;
69    while (child = children.pop()) {
70      row.push(child);
71      row.area += child.area;
72      if (child.z != null) {
73        position(row, child.z ? rect.dx : rect.dy, rect, !children.length);
74        row.length = row.area = 0;
75      }
76    }
77    node.children.forEach(stickify);
78  }
79
80  // Computes the score for the specified row, as the worst aspect ratio.
81  function worst(row, u) {
82    var s = row.area,
83        r,
84        rmax = 0,
85        rmin = Infinity,
86        i = -1,
87        n = row.length;
88    while (++i < n) {
89      if (!(r = row[i].area)) continue;
90      if (r < rmin) rmin = r;
91      if (r > rmax) rmax = r;
92    }
93    s *= s;
94    u *= u;
95    return s
96        ? Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio))
97        : Infinity;
98  }
99
100  // Positions the specified row of nodes. Modifies `rect`.
101  function position(row, u, rect, flush) {
102    var i = -1,
103        n = row.length,
104        x = rect.x,
105        y = rect.y,
106        v = u ? round(row.area / u) : 0,
107        o;
108    if (u == rect.dx) { // horizontal subdivision
109      if (flush || v > rect.dy) v = rect.dy; // over+underflow
110      while (++i < n) {
111        o = row[i];
112        o.x = x;
113        o.y = y;
114        o.dy = v;
115        x += o.dx = v ? round(o.area / v) : 0;
116      }
117      o.z = true;
118      o.dx += rect.x + rect.dx - x; // rounding error
119      rect.y += v;
120      rect.dy -= v;
121    } else { // vertical subdivision
122      if (flush || v > rect.dx) v = rect.dx; // over+underflow
123      while (++i < n) {
124        o = row[i];
125        o.x = x;
126        o.y = y;
127        o.dx = v;
128        y += o.dy = v ? round(o.area / v) : 0;
129      }
130      o.z = false;
131      o.dy += rect.y + rect.dy - y; // rounding error
132      rect.x += v;
133      rect.dx -= v;
134    }
135  }
136
137  function treemap(d) {
138    var nodes = stickies || hierarchy(d),
139        root = nodes[0];
140    root.x = 0;
141    root.y = 0;
142    root.dx = size[0];
143    root.dy = size[1];
144    if (stickies) hierarchy.revalue(root);
145    scale([root], root.dx * root.dy / root.value);
146    (stickies ? stickify : squarify)(root);
147    if (sticky) stickies = nodes;
148    return nodes;
149  }
150
151  treemap.size = function(x) {
152    if (!arguments.length) return size;
153    size = x;
154    return treemap;
155  };
156
157  treemap.padding = function(x) {
158    if (!arguments.length) return padding;
159
160    function padFunction(node) {
161      var p = x.call(treemap, node, node.depth);
162      return p == null
163          ? d3_layout_treemapPadNull(node)
164          : d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p);
165    }
166
167    function padConstant(node) {
168      return d3_layout_treemapPad(node, x);
169    }
170
171    var type;
172    pad = (padding = x) == null ? d3_layout_treemapPadNull
173        : (type = typeof x) === "function" ? padFunction
174        : type === "number" ? (x = [x, x, x, x], padConstant)
175        : padConstant;
176    return treemap;
177  };
178
179  treemap.round = function(x) {
180    if (!arguments.length) return round != Number;
181    round = x ? Math.round : Number;
182    return treemap;
183  };
184
185  treemap.sticky = function(x) {
186    if (!arguments.length) return sticky;
187    sticky = x;
188    stickies = null;
189    return treemap;
190  };
191
192  treemap.ratio = function(x) {
193    if (!arguments.length) return ratio;
194    ratio = x;
195    return treemap;
196  };
197
198  return d3_layout_hierarchyRebind(treemap, hierarchy);
199};
200
201function d3_layout_treemapPadNull(node) {
202  return {x: node.x, y: node.y, dx: node.dx, dy: node.dy};
203}
204
205function d3_layout_treemapPad(node, padding) {
206  var x = node.x + padding[3],
207      y = node.y + padding[0],
208      dx = node.dx - padding[1] - padding[3],
209      dy = node.dy - padding[0] - padding[2];
210  if (dx < 0) { x += dx / 2; dx = 0; }
211  if (dy < 0) { y += dy / 2; dy = 0; }
212  return {x: x, y: y, dx: dx, dy: dy};
213}
Note: See TracBrowser for help on using the repository browser.