OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2015 Google Inc. All rights reserved. | 2 * Copyright 2015 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style | 4 * Use of this source code is governed by a BSD-style |
5 * license that can be found in the LICENSE file or at | 5 * license that can be found in the LICENSE file or at |
6 * https://developers.google.com/open-source/licenses/bsd | 6 * https://developers.google.com/open-source/licenses/bsd |
7 */ | 7 */ |
8 part of charted.layout; | 8 part of charted.layout; |
9 | 9 |
10 /// PaddingFunction takes a node and generates the padding for the particular | 10 /// PaddingFunction takes a node and generates the padding for the particular |
11 /// node | 11 /// node |
12 typedef List PaddingFunction(TreeMapNode node); | 12 typedef List PaddingFunction(TreeMapNode node); |
13 | 13 |
14 /** | 14 /** |
15 * Utility layout class which recursively subdivides area into rectangles which | 15 * Utility layout class which recursively subdivides area into rectangles which |
16 * can be used to quickly visualize the size of any node in the tree. | 16 * can be used to quickly visualize the size of any node in the tree. |
17 */ | 17 */ |
18 class TreeMapLayout extends HierarchyLayout { | 18 class TreeMapLayout extends HierarchyLayout { |
19 /// Rectangular subdivision; squareness controlled via the target ratio. | 19 /// Rectangular subdivision; squareness controlled via the target ratio. |
20 static const TREEMAP_LAYOUT_SQUARIFY = 0; | 20 static const TREEMAP_LAYOUT_SQUARIFY = 0; |
21 | 21 |
22 /// Horizontal subdivision. | 22 /// Horizontal subdivision. |
23 static const TREEMAP_LAYOUT_SLICE= 1; | 23 static const TREEMAP_LAYOUT_SLICE = 1; |
24 | 24 |
25 /// Vertical subdivision. | 25 /// Vertical subdivision. |
26 static const TREEMAP_LAYOUT_DICE = 2; | 26 static const TREEMAP_LAYOUT_DICE = 2; |
27 | 27 |
28 /// Alternating between horizontal and vertical subdivision. | 28 /// Alternating between horizontal and vertical subdivision. |
29 static const TREEMAP_LAYOUT_SLICE_DICE = 3; | 29 static const TREEMAP_LAYOUT_SLICE_DICE = 3; |
30 | 30 |
31 static const _DEFAULT_PADDING = const [0, 0, 0, 0]; | 31 static const _DEFAULT_PADDING = const [0, 0, 0, 0]; |
32 | 32 |
33 /// A sticky treemap layout will preserve the relative arrangement of nodes | 33 /// A sticky treemap layout will preserve the relative arrangement of nodes |
34 /// across transitions. (not yet implemented) | 34 /// across transitions. (not yet implemented) |
35 bool _sticky = false; | 35 bool _sticky = false; |
36 | 36 |
37 /// The available layout size to the specified two-element array of numbers | 37 /// The available layout size to the specified two-element array of numbers |
38 /// representing width and height. | 38 /// representing width and height. |
39 List size = [1, 1]; | 39 List size = [1, 1]; |
40 | 40 |
41 /// The mode to layout the Treemap. | 41 /// The mode to layout the Treemap. |
42 int mode = TREEMAP_LAYOUT_SQUARIFY; | 42 int mode = TREEMAP_LAYOUT_SQUARIFY; |
43 | 43 |
44 /// The ration to scale the Treemap. | 44 /// The ration to scale the Treemap. |
45 num ratio = .5 * (1 + math.sqrt(5)); | 45 num ratio = .5 * (1 + math.sqrt(5)); |
46 | 46 |
47 /// The paddingFunction for each node, defaults to return [0, 0, 0, 0]. | 47 /// The paddingFunction for each node, defaults to return [0, 0, 0, 0]. |
48 PaddingFunction paddingFunction = (node) => _DEFAULT_PADDING; | 48 PaddingFunction paddingFunction = (node) => _DEFAULT_PADDING; |
49 | 49 |
50 /// TODO(midoringo): Implement sticky related feature. | 50 /// TODO(midoringo): Implement sticky related feature. |
51 get sticky => _sticky; | 51 get sticky => _sticky; |
52 set sticky (bool sticky) { | 52 set sticky(bool sticky) { |
53 _sticky = sticky; | 53 _sticky = sticky; |
54 } | 54 } |
55 | 55 |
56 // TODO (midoringo): handle the sticky case. | 56 // TODO (midoringo): handle the sticky case. |
57 @override | 57 @override |
58 List<TreeMapNode> layout(List rows, int parentColumn, int labelColumn, | 58 List<TreeMapNode> layout( |
59 int valueColumn) { | 59 List rows, int parentColumn, int labelColumn, int valueColumn) { |
60 var nodes = super.layout(rows, parentColumn, labelColumn, valueColumn); | 60 var nodes = super.layout(rows, parentColumn, labelColumn, valueColumn); |
61 var root = nodes[0]; | 61 var root = nodes[0]; |
62 root.x = 0; | 62 root.x = 0; |
63 root.y = 0; | 63 root.y = 0; |
64 root.dx = size.first; | 64 root.dx = size.first; |
65 root.dy = size.last; | 65 root.dy = size.last; |
66 _scale([root], root.dx * root.dy / root.value); | 66 _scale([root], root.dx * root.dy / root.value); |
67 _squarify(root); | 67 _squarify(root); |
68 return nodes; | 68 return nodes; |
69 } | 69 } |
70 | 70 |
71 @override | 71 @override |
72 TreeMapNode createNode(label, value, depth) { | 72 TreeMapNode createNode(label, value, depth) { |
73 return new TreeMapNode() | 73 return new TreeMapNode() |
74 ..label = label | 74 ..label = label |
75 ..value = value | 75 ..value = value |
76 ..depth = depth; | 76 ..depth = depth; |
77 } | 77 } |
78 | 78 |
79 void _position(List<TreeMapNode> nodes, num length, MutableRect rect, | 79 void _position(List<TreeMapNode> nodes, num length, MutableRect rect, |
80 bool flush, num area) { | 80 bool flush, num area) { |
81 var x = rect.x; | 81 var x = rect.x; |
82 var y = rect.y; | 82 var y = rect.y; |
83 var v = length > 0 ? (area / length).round() : 0; | 83 var v = length > 0 ? (area / length).round() : 0; |
84 if (length == rect.width) { | 84 if (length == rect.width) { |
85 if (flush || (v > rect.height)) v = rect.height; | 85 if (flush || (v > rect.height)) v = rect.height; |
86 for (var node in nodes) { | 86 for (var node in nodes) { |
87 node.x = x; | 87 node.x = x; |
88 node.y = y; | 88 node.y = y; |
89 node.dy = v; | 89 node.dy = v; |
90 x += node.dx = math.min(rect.x + rect.width - x, v > 0 ? | 90 x += node.dx = math.min( |
91 (node.area / v).round() : 0); | 91 rect.x + rect.width - x, v > 0 ? (node.area / v).round() : 0); |
92 } | 92 } |
93 nodes.last.sticky = true; | 93 nodes.last.sticky = true; |
94 nodes.last.dx += rect.x + rect.width - x; | 94 nodes.last.dx += rect.x + rect.width - x; |
95 rect.y += v; | 95 rect.y += v; |
96 rect.height -= v; | 96 rect.height -= v; |
97 } else { | 97 } else { |
98 if (flush || (v > rect.width)) v = rect.width; | 98 if (flush || (v > rect.width)) v = rect.width; |
99 for (var node in nodes) { | 99 for (var node in nodes) { |
100 node.x = x; | 100 node.x = x; |
101 node.y = y; | 101 node.y = y; |
102 node.dx = v; | 102 node.dx = v; |
103 y += node.dy = math.min(rect.y + rect.height - y, v > 0 ? | 103 y += node.dy = math.min( |
104 (node.area / v).round() : 0); | 104 rect.y + rect.height - y, v > 0 ? (node.area / v).round() : 0); |
105 } | 105 } |
106 nodes.last.sticky = false; | 106 nodes.last.sticky = false; |
107 nodes.last.dy += rect.y + rect.height - y; | 107 nodes.last.dy += rect.y + rect.height - y; |
108 rect.x += v; | 108 rect.x += v; |
109 rect.width -= v; | 109 rect.width -= v; |
110 } | 110 } |
111 } | 111 } |
112 | 112 |
113 /// Applies padding between each nodes. | 113 /// Applies padding between each nodes. |
114 MutableRect _treeMapPad(TreeMapNode node, padding) { | 114 MutableRect _treeMapPad(TreeMapNode node, padding) { |
(...skipping 27 matching lines...) Expand all Loading... |
142 var rmax = 0; | 142 var rmax = 0; |
143 var rmin = double.INFINITY; | 143 var rmin = double.INFINITY; |
144 for (var node in nodes) { | 144 for (var node in nodes) { |
145 area = node.area; | 145 area = node.area; |
146 if (area <= 0) continue; | 146 if (area <= 0) continue; |
147 if (area < rmin) rmin = area; | 147 if (area < rmin) rmin = area; |
148 if (area > rmax) rmax = area; | 148 if (area > rmax) rmax = area; |
149 } | 149 } |
150 pArea *= pArea; | 150 pArea *= pArea; |
151 length *= length; | 151 length *= length; |
152 return (pArea > 0) ? math.max(length * rmax * ratio / pArea, | 152 return (pArea > 0) |
153 pArea / (length * rmin * ratio)) : double.INFINITY; | 153 ? math.max( |
| 154 length * rmax * ratio / pArea, pArea / (length * rmin * ratio)) |
| 155 : double.INFINITY; |
154 } | 156 } |
155 | 157 |
156 /// Recursively compute each nodes (and its children nodes) position and size | 158 /// Recursively compute each nodes (and its children nodes) position and size |
157 /// base on the node's property and layout mode. | 159 /// base on the node's property and layout mode. |
158 void _squarify(TreeMapNode node) { | 160 void _squarify(TreeMapNode node) { |
159 var children = node.children; | 161 var children = node.children; |
160 if (children.isNotEmpty) { | 162 if (children.isNotEmpty) { |
161 var rect = _treeMapPad(node, paddingFunction(node)); | 163 var rect = _treeMapPad(node, paddingFunction(node)); |
162 List<TreeMapNode> nodes = []; | 164 List<TreeMapNode> nodes = []; |
163 var area = 0; | 165 var area = 0; |
164 var remaining = new List.from(children); | 166 var remaining = new List.from(children); |
165 var score, n, | 167 var score, |
166 best = double.INFINITY, | 168 n, |
167 length = (mode == TREEMAP_LAYOUT_SLICE) ? rect.width : | 169 best = double.INFINITY, |
168 (mode == TREEMAP_LAYOUT_DICE) ? rect.height : | 170 length = (mode == TREEMAP_LAYOUT_SLICE) |
169 (mode == TREEMAP_LAYOUT_SLICE_DICE) ? (node.depth & 1 == 1) ? | 171 ? rect.width |
170 rect.height : rect.width : math.min(rect.width, rect.height); | 172 : (mode == TREEMAP_LAYOUT_DICE) |
| 173 ? rect.height |
| 174 : (mode == TREEMAP_LAYOUT_SLICE_DICE) |
| 175 ? (node.depth & 1 == 1) ? rect.height : rect.width |
| 176 : math.min(rect.width, rect.height); |
171 _scale(remaining, rect.width * rect.height / node.value); | 177 _scale(remaining, rect.width * rect.height / node.value); |
172 while ((n = remaining.length) > 0) { | 178 while ((n = remaining.length) > 0) { |
173 var child = remaining[n - 1]; | 179 var child = remaining[n - 1]; |
174 nodes.add(child); | 180 nodes.add(child); |
175 area += child.area; | 181 area += child.area; |
176 score = _worst(nodes, length, area); | 182 score = _worst(nodes, length, area); |
177 if (mode != TREEMAP_LAYOUT_SQUARIFY || score <= best) { | 183 if (mode != TREEMAP_LAYOUT_SQUARIFY || score <= best) { |
178 remaining.removeLast(); | 184 remaining.removeLast(); |
179 best = score; | 185 best = score; |
180 } else { | 186 } else { |
(...skipping 27 matching lines...) Expand all Loading... |
208 | 214 |
209 /// The y-extent of the node position. | 215 /// The y-extent of the node position. |
210 num dy = 0; | 216 num dy = 0; |
211 | 217 |
212 /// The area the node should take up. | 218 /// The area the node should take up. |
213 num area = 0; | 219 num area = 0; |
214 | 220 |
215 /// Attribute for the last node in the row, only used for sticky layout. | 221 /// Attribute for the last node in the row, only used for sticky layout. |
216 bool sticky = false; | 222 bool sticky = false; |
217 } | 223 } |
OLD | NEW |