1 /** 2 * Constructs a new, empty cluster 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 Implements a hierarchical layout using the cluster (or dendrogram) 7 * algorithm. This layout provides both node-link and space-filling 8 * implementations of cluster diagrams. In many ways it is similar to 9 * {@link pv.Layout.Partition}, except that leaf nodes are positioned at maximum 10 * depth, and the depth of internal nodes is based on their distance from their 11 * deepest descendant, rather than their distance from the root. 12 * 13 * <p>The cluster layout supports a "group" property, which if true causes 14 * siblings to be positioned closer together than unrelated nodes at the same 15 * depth. Unlike the partition layout, this layout does not support dynamic 16 * sizing for leaf nodes; all leaf nodes are the same size. 17 * 18 * <p>For more details on how to use this layout, see 19 * {@link pv.Layout.Hierarchy}. 20 * 21 * @see pv.Layout.Cluster.Fill 22 * @extends pv.Layout.Hierarchy 23 */ 24 pv.Layout.Cluster = function() { 25 pv.Layout.Hierarchy.call(this); 26 var interpolate, // cached interpolate 27 buildImplied = this.buildImplied; 28 29 /** @private Cache layout state to optimize properties. */ 30 this.buildImplied = function(s) { 31 buildImplied.call(this, s); 32 interpolate 33 = /^(top|bottom)$/.test(s.orient) ? "step-before" 34 : /^(left|right)$/.test(s.orient) ? "step-after" 35 : "linear"; 36 }; 37 38 this.link.interpolate(function() { return interpolate; }); 39 }; 40 41 pv.Layout.Cluster.prototype = pv.extend(pv.Layout.Hierarchy) 42 .property("group", Number) 43 .property("orient", String) 44 .property("innerRadius", Number) 45 .property("outerRadius", Number); 46 47 /** 48 * The group parameter; defaults to 0, disabling grouping of siblings. If this 49 * parameter is set to a positive number (or true, which is equivalent to 1), 50 * then additional space will be allotted between sibling groups. In other 51 * words, siblings (nodes that share the same parent) will be positioned more 52 * closely than nodes at the same depth that do not share a parent. 53 * 54 * @type number 55 * @name pv.Layout.Cluster.prototype.group 56 */ 57 58 /** 59 * The orientation. The default orientation is "top", which means that the root 60 * node is placed on the top edge, leaf nodes appear on the bottom edge, and 61 * internal nodes are in-between. The following orientations are supported:<ul> 62 * 63 * <li>left - left-to-right. 64 * <li>right - right-to-left. 65 * <li>top - top-to-bottom. 66 * <li>bottom - bottom-to-top. 67 * <li>radial - radially, with the root at the center.</ul> 68 * 69 * @type string 70 * @name pv.Layout.Cluster.prototype.orient 71 */ 72 73 /** 74 * The inner radius; defaults to 0. This property applies only to radial 75 * orientations, and can be used to compress the layout radially. Note that for 76 * the node-link implementation, the root node is always at the center, 77 * regardless of the value of this property; this property only affects internal 78 * and leaf nodes. For the space-filling implementation, a non-zero value of 79 * this property will result in the root node represented as a ring rather than 80 * a circle. 81 * 82 * @type number 83 * @name pv.Layout.Cluster.prototype.innerRadius 84 */ 85 86 /** 87 * The outer radius; defaults to fill the containing panel, based on the height 88 * and width of the layout. If the layout has no height and width specified, it 89 * will extend to fill the enclosing panel. 90 * 91 * @type number 92 * @name pv.Layout.Cluster.prototype.outerRadius 93 */ 94 95 /** 96 * Defaults for cluster layouts. The default group parameter is 0 and the 97 * default orientation is "top". 98 * 99 * @type pv.Layout.Cluster 100 */ 101 pv.Layout.Cluster.prototype.defaults = new pv.Layout.Cluster() 102 .extend(pv.Layout.Hierarchy.prototype.defaults) 103 .group(0) 104 .orient("top"); 105 106 /** @private */ 107 pv.Layout.Cluster.prototype.buildImplied = function(s) { 108 if (pv.Layout.Hierarchy.prototype.buildImplied.call(this, s)) return; 109 110 var root = s.nodes[0], 111 group = s.group, 112 breadth, 113 depth, 114 leafCount = 0, 115 leafIndex = .5 - group / 2; 116 117 /* Count the leaf nodes and compute the depth of descendants. */ 118 var p = undefined; 119 root.visitAfter(function(n) { 120 if (n.firstChild) { 121 n.depth = 1 + pv.max(n.childNodes, function(n) { return n.depth; }); 122 } else { 123 if (group && (p != n.parentNode)) { 124 p = n.parentNode; 125 leafCount += group; 126 } 127 leafCount++; 128 n.depth = 0; 129 } 130 }); 131 breadth = 1 / leafCount; 132 depth = 1 / root.depth; 133 134 /* Compute the unit breadth and depth of each node. */ 135 var p = undefined; 136 root.visitAfter(function(n) { 137 if (n.firstChild) { 138 n.breadth = pv.mean(n.childNodes, function(n) { return n.breadth; }); 139 } else { 140 if (group && (p != n.parentNode)) { 141 p = n.parentNode; 142 leafIndex += group; 143 } 144 n.breadth = breadth * leafIndex++; 145 } 146 n.depth = 1 - n.depth * depth; 147 }); 148 149 /* Compute breadth and depth ranges for space-filling layouts. */ 150 root.visitAfter(function(n) { 151 n.minBreadth = n.firstChild 152 ? n.firstChild.minBreadth 153 : (n.breadth - breadth / 2); 154 n.maxBreadth = n.firstChild 155 ? n.lastChild.maxBreadth 156 : (n.breadth + breadth / 2); 157 }); 158 root.visitBefore(function(n) { 159 n.minDepth = n.parentNode 160 ? n.parentNode.maxDepth 161 : 0; 162 n.maxDepth = n.parentNode 163 ? (n.depth + root.depth) 164 : (n.minDepth + 2 * root.depth); 165 }); 166 root.minDepth = -depth; 167 168 pv.Layout.Hierarchy.NodeLink.buildImplied.call(this, s); 169 }; 170 171 /** 172 * Constructs a new, empty space-filling cluster layout. Layouts are not 173 * typically constructed directly; instead, they are added to an existing panel 174 * via {@link pv.Mark#add}. 175 * 176 * @class A variant of cluster layout that is space-filling. The meaning of the 177 * exported mark prototypes changes slightly in the space-filling 178 * implementation:<ul> 179 * 180 * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Bar} for 181 * non-radial orientations, and a {@link pv.Wedge} for radial orientations. 182 * 183 * <p><li><tt>link</tt> - unsupported; undefined. Links are encoded implicitly 184 * in the arrangement of the space-filling nodes. 185 * 186 * <p><li><tt>label</tt> - for rendering node labels; typically a 187 * {@link pv.Label}. 188 * 189 * </ul>For more details on how to use this layout, see 190 * {@link pv.Layout.Cluster}. 191 * 192 * @extends pv.Layout.Cluster 193 */ 194 pv.Layout.Cluster.Fill = function() { 195 pv.Layout.Cluster.call(this); 196 pv.Layout.Hierarchy.Fill.constructor.call(this); 197 }; 198 199 pv.Layout.Cluster.Fill.prototype = pv.extend(pv.Layout.Cluster); 200 201 /** @private */ 202 pv.Layout.Cluster.Fill.prototype.buildImplied = function(s) { 203 if (pv.Layout.Cluster.prototype.buildImplied.call(this, s)) return; 204 pv.Layout.Hierarchy.Fill.buildImplied.call(this, s); 205 }; 206