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