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