1 /** 2 * Constructs a new, empty partition layout. Layouts are not typically 3 * constructed directly; instead, they are added to an existing panel via 4 * {@link pv.Mark#add}. 5 * 6 * @class Implemeents a hierarchical layout using the partition (or sunburst, 7 * icicle) algorithm. This layout provides both node-link and space-filling 8 * implementations of partition diagrams. In many ways it is similar to 9 * {@link pv.Layout.Cluster}, except that leaf nodes are positioned based on 10 * their distance from the root. 11 * 12 * <p>The partition layout support dynamic sizing for leaf nodes, if a 13 * {@link #size} psuedo-property is specified. The default size function returns 14 * 1, causing all leaf nodes to be sized equally, and all internal nodes to be 15 * sized by the number of leaf nodes they have as descendants. 16 * 17 * <p>The size function can be used in conjunction with the order property, 18 * which allows the nodes to the sorted by the computed size. Note: for sorting 19 * based on other data attributes, simply use the default <tt>null</tt> for the 20 * order property, and sort the nodes beforehand using the {@link pv.Dom} 21 * operator. 22 * 23 * <p>For more details on how to use this layout, see 24 * {@link pv.Layout.Hierarchy}. 25 * 26 * @see pv.Layout.Partition.Fill 27 * @extends pv.Layout.Hierarchy 28 */ 29 pv.Layout.Partition = function() { 30 pv.Layout.Hierarchy.call(this); 31 }; 32 33 pv.Layout.Partition.prototype = pv.extend(pv.Layout.Hierarchy) 34 .property("order", String) // null, ascending, descending? 35 .property("orient", String) // top, left, right, bottom, radial 36 .property("innerRadius", Number) 37 .property("outerRadius", Number); 38 39 /** 40 * The sibling node order. The default order is <tt>null</tt>, which means to 41 * use the sibling order specified by the nodes property as-is. A value of 42 * "ascending" will sort siblings in ascending order of size, while "descending" 43 * will do the reverse. For sorting based on data attributes other than size, 44 * use the default <tt>null</tt> for the order property, and sort the nodes 45 * beforehand using the {@link pv.Dom} operator. 46 * 47 * @see pv.Dom.Node#sort 48 * @type string 49 * @name pv.Layout.Partition.prototype.order 50 */ 51 52 /** 53 * The orientation. The default orientation is "top", which means that the root 54 * node is placed on the top edge, leaf nodes appear at the bottom, and internal 55 * nodes are in-between. The following orientations are supported:<ul> 56 * 57 * <li>left - left-to-right. 58 * <li>right - right-to-left. 59 * <li>top - top-to-bottom. 60 * <li>bottom - bottom-to-top. 61 * <li>radial - radially, with the root at the center.</ul> 62 * 63 * @type string 64 * @name pv.Layout.Partition.prototype.orient 65 */ 66 67 /** 68 * The inner radius; defaults to 0. This property applies only to radial 69 * orientations, and can be used to compress the layout radially. Note that for 70 * the node-link implementation, the root node is always at the center, 71 * regardless of the value of this property; this property only affects internal 72 * and leaf nodes. For the space-filling implementation, a non-zero value of 73 * this property will result in the root node represented as a ring rather than 74 * a circle. 75 * 76 * @type number 77 * @name pv.Layout.Partition.prototype.innerRadius 78 */ 79 80 /** 81 * The outer radius; defaults to fill the containing panel, based on the height 82 * and width of the layout. If the layout has no height and width specified, it 83 * will extend to fill the enclosing panel. 84 * 85 * @type number 86 * @name pv.Layout.Partition.prototype.outerRadius 87 */ 88 89 /** 90 * Default properties for partition layouts. The default orientation is "top". 91 * 92 * @type pv.Layout.Partition 93 */ 94 pv.Layout.Partition.prototype.defaults = new pv.Layout.Partition() 95 .extend(pv.Layout.Hierarchy.prototype.defaults) 96 .orient("top"); 97 98 /** @private */ 99 pv.Layout.Partition.prototype.$size = function() { return 1; }; 100 101 /** 102 * Specifies the sizing function. By default, a sizing function is disabled and 103 * all nodes are given constant size. The sizing function is invoked for each 104 * leaf node in the tree (passed to the constructor). 105 * 106 * <p>For example, if the tree data structure represents a file system, with 107 * files as leaf nodes, and each file has a <tt>bytes</tt> attribute, you can 108 * specify a size function as: 109 * 110 * <pre> .size(function(d) d.bytes)</pre> 111 * 112 * As with other properties, a size function may specify additional arguments to 113 * access the data associated with the layout and any enclosing panels. 114 * 115 * @param {function} f the new sizing function. 116 * @returns {pv.Layout.Partition} this. 117 */ 118 pv.Layout.Partition.prototype.size = function(f) { 119 this.$size = f; 120 return this; 121 }; 122 123 /** @private */ 124 pv.Layout.Partition.prototype.buildImplied = function(s) { 125 if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; 126 127 var that = this, 128 root = s.nodes[0], 129 stack = pv.Mark.stack, 130 maxDepth = 0; 131 132 /* Recursively compute the tree depth and node size. */ 133 stack.unshift(null); 134 root.visitAfter(function(n, i) { 135 if (i > maxDepth) maxDepth = i; 136 n.size = n.firstChild 137 ? pv.sum(n.childNodes, function(n) { return n.size; }) 138 : that.$size.apply(that, (stack[0] = n, stack)); 139 }); 140 stack.shift(); 141 142 /* Order */ 143 switch (s.order) { 144 case "ascending": root.sort(function(a, b) { return a.size - b.size; }); break; 145 case "descending": root.sort(function(b, a) { return a.size - b.size; }); break; 146 } 147 148 /* Compute the unit breadth and depth of each node. */ 149 var ds = 1 / maxDepth; 150 root.minBreadth = 0; 151 root.breadth = .5; 152 root.maxBreadth = 1; 153 root.visitBefore(function(n) { 154 var b = n.minBreadth, s = n.maxBreadth - b; 155 for (var c = n.firstChild; c; c = c.nextSibling) { 156 c.minBreadth = b; 157 c.maxBreadth = b += (c.size / n.size) * s; 158 c.breadth = (b + c.minBreadth) / 2; 159 } 160 }); 161 root.visitAfter(function(n, i) { 162 n.minDepth = (i - 1) * ds; 163 n.maxDepth = n.depth = i * ds; 164 }); 165 166 pv.Layout.Hierarchy.NodeLink.buildImplied.call(this, s); 167 }; 168 169 /** 170 * Constructs a new, empty space-filling partition layout. Layouts are not 171 * typically constructed directly; instead, they are added to an existing panel 172 * via {@link pv.Mark#add}. 173 * 174 * @class A variant of partition layout that is space-filling. The meaning of 175 * the exported mark prototypes changes slightly in the space-filling 176 * implementation:<ul> 177 * 178 * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Bar} for 179 * non-radial orientations, and a {@link pv.Wedge} for radial orientations. 180 * 181 * <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly 182 * in the arrangement of the space-filling nodes. 183 * 184 * <p><li><tt>label</tt> - for rendering node labels; typically a 185 * {@link pv.Label}. 186 * 187 * </ul>For more details on how to use this layout, see 188 * {@link pv.Layout.Partition}. 189 * 190 * @extends pv.Layout.Partition 191 */ 192 pv.Layout.Partition.Fill = function() { 193 pv.Layout.Partition.call(this); 194 pv.Layout.Hierarchy.Fill.constructor.call(this); 195 }; 196 197 pv.Layout.Partition.Fill.prototype = pv.extend(pv.Layout.Partition); 198 199 /** @private */ 200 pv.Layout.Partition.Fill.prototype.buildImplied = function(s) { 201 if (pv.Layout.Partition.prototype.buildImplied.call(this, s)) return; 202 pv.Layout.Hierarchy.Fill.buildImplied.call(this, s); 203 }; 204