1 /**
  2  * Constructs a new, empty network layout. Layouts are not typically constructed
  3  * directly; instead, they are added to an existing panel via
  4  * {@link pv.Mark#add}.
  5  *
  6  * @class Represents an abstract layout for network diagrams. This class
  7  * provides the basic structure for both node-link diagrams (such as
  8  * force-directed graph layout) and space-filling network diagrams (such as
  9  * sunbursts and treemaps). Note that "network" here is a general term that
 10  * includes hierarchical structures; a tree is represented using links from
 11  * child to parent.
 12  *
 13  * <p>Network layouts require the graph data structure to be defined using two
 14  * properties:<ul>
 15  *
 16  * <li><tt>nodes</tt> - an array of objects representing nodes. Objects in this
 17  * array must conform to the {@link pv.Layout.Network.Node} interface; which is
 18  * to say, be careful to avoid naming collisions with automatic attributes such
 19  * as <tt>index</tt> and <tt>linkDegree</tt>. If the nodes property is defined
 20  * as an array of primitives, such as numbers or strings, these primitives are
 21  * automatically wrapped in an object; the resulting object's <tt>nodeValue</tt>
 22  * attribute points to the original primitive value.
 23  *
 24  * <p><li><tt>links</tt> - an array of objects representing links. Objects in
 25  * this array must conform to the {@link pv.Layout.Network.Link} interface; at a
 26  * minimum, either <tt>source</tt> and <tt>target</tt> indexes or
 27  * <tt>sourceNode</tt> and <tt>targetNode</tt> references must be set. Note that
 28  * if the links property is defined after the nodes property, the links can be
 29  * defined in terms of <tt>this.nodes()</tt>.
 30  *
 31  * </ul>
 32  *
 33  * <p>Three standard mark prototypes are provided:<ul>
 34  *
 35  * <li><tt>node</tt> - for rendering nodes; typically a {@link pv.Dot}. The node
 36  * mark is added directly to the layout, with the data property defined via the
 37  * layout's <tt>nodes</tt> property. Properties such as <tt>strokeStyle</tt> and
 38  * <tt>fillStyle</tt> can be overridden to compute properties from node data
 39  * dynamically.
 40  *
 41  * <p><li><tt>link</tt> - for rendering links; typically a {@link pv.Line}. The
 42  * link mark is added to a child panel, whose data property is defined as
 43  * layout's <tt>links</tt> property. The link's data property is then a
 44  * two-element array of the source node and target node. Thus, poperties such as
 45  * <tt>strokeStyle</tt> and <tt>fillStyle</tt> can be overridden to compute
 46  * properties from either the node data (the first argument) or the link data
 47  * (the second argument; the parent panel data) dynamically.
 48  *
 49  * <p><li><tt>label</tt> - for rendering node labels; typically a
 50  * {@link pv.Label}. The label mark is added directly to the layout, with the
 51  * data property defined via the layout's <tt>nodes</tt> property. Properties
 52  * such as <tt>strokeStyle</tt> and <tt>fillStyle</tt> can be overridden to
 53  * compute properties from node data dynamically.
 54  *
 55  * </ul>Note that some network implementations may not support all three
 56  * standard mark prototypes; for example, space-filling hierarchical layouts
 57  * typically do not use a <tt>link</tt> prototype, as the parent-child links are
 58  * implied by the structure of the space-filling <tt>node</tt> marks.  Check the
 59  * specific network layout for implementation details.
 60  *
 61  * <p>Network layout properties, including <tt>nodes</tt> and <tt>links</tt>,
 62  * are typically cached rather than re-evaluated with every call to render. This
 63  * is a performance optimization, as network layout algorithms can be
 64  * expensive. If the network structure changes, call {@link #reset} to clear the
 65  * cache before rendering. Note that although the network layout properties are
 66  * cached, child mark properties, such as the marks used to render the nodes and
 67  * links, <i>are not</i>. Therefore, non-structural changes to the network
 68  * layout, such as changing the color of a mark on mouseover, do not need to
 69  * reset the layout.
 70  *
 71  * @see pv.Layout.Hierarchy
 72  * @see pv.Layout.Force
 73  * @see pv.Layout.Matrix
 74  * @see pv.Layout.Arc
 75  * @see pv.Layout.Rollup
 76  * @extends pv.Layout
 77  */
 78 pv.Layout.Network = function() {
 79   pv.Layout.call(this);
 80   var that = this;
 81 
 82   /* @private Version tracking to cache layout state, improving performance. */
 83   this.$id = pv.id();
 84 
 85   /**
 86    * The node prototype. This prototype is intended to be used with a Dot mark
 87    * in conjunction with the link prototype.
 88    *
 89    * @type pv.Mark
 90    * @name pv.Layout.Network.prototype.node
 91    */
 92   (this.node = new pv.Mark()
 93       .data(function() { return that.nodes(); })
 94       .strokeStyle("#1f77b4")
 95       .fillStyle("#fff")
 96       .left(function(n) { return n.x; })
 97       .top(function(n) { return n.y; })).parent = this;
 98 
 99   /**
100    * The link prototype, which renders edges between source nodes and target
101    * nodes. This prototype is intended to be used with a Line mark in
102    * conjunction with the node prototype.
103    *
104    * @type pv.Mark
105    * @name pv.Layout.Network.prototype.link
106    */
107   this.link = new pv.Mark()
108       .extend(this.node)
109       .data(function(p) { return [p.sourceNode, p.targetNode]; })
110       .fillStyle(null)
111       .lineWidth(function(d, p) { return p.linkValue * 1.5; })
112       .strokeStyle("rgba(0,0,0,.2)");
113 
114   this.link.add = function(type) {
115     return that.add(pv.Panel)
116         .data(function() { return that.links(); })
117       .add(type)
118         .extend(this);
119   };
120 
121   /**
122    * The node label prototype, which renders the node name adjacent to the node.
123    * This prototype is provided as an alternative to using the anchor on the
124    * node mark; it is primarily intended to be used with radial node-link
125    * layouts, since it provides a convenient mechanism to set the text angle.
126    *
127    * @type pv.Mark
128    * @name pv.Layout.Network.prototype.label
129    */
130   (this.label = new pv.Mark()
131       .extend(this.node)
132       .textMargin(7)
133       .textBaseline("middle")
134       .text(function(n) { return n.nodeName || n.nodeValue; })
135       .textAngle(function(n) {
136           var a = n.midAngle;
137           return pv.Wedge.upright(a) ? a : (a + Math.PI);
138         })
139       .textAlign(function(n) {
140           return pv.Wedge.upright(n.midAngle) ? "left" : "right";
141         })).parent = this;
142 };
143 
144 /**
145  * @class Represents a node in a network layout. There is no explicit
146  * constructor; this class merely serves to document the attributes that are
147  * used on nodes in network layouts. (Note that hierarchical nodes place
148  * additional requirements on node representation, vis {@link pv.Dom.Node}.)
149  *
150  * @see pv.Layout.Network
151  * @name pv.Layout.Network.Node
152  */
153 
154 /**
155  * The node index, zero-based. This attribute is populated automatically based
156  * on the index in the array returned by the <tt>nodes</tt> property.
157  *
158  * @type number
159  * @name pv.Layout.Network.Node.prototype.index
160  */
161 
162 /**
163  * The link degree; the sum of link values for all incoming and outgoing links.
164  * This attribute is populated automatically.
165  *
166  * @type number
167  * @name pv.Layout.Network.Node.prototype.linkDegree
168  */
169 
170 /**
171  * The node name; optional. If present, this attribute will be used to provide
172  * the text for node labels. If not present, the label text will fallback to the
173  * <tt>nodeValue</tt> attribute.
174  *
175  * @type string
176  * @name pv.Layout.Network.Node.prototype.nodeName
177  */
178 
179 /**
180  * The node value; optional. If present, and no <tt>nodeName</tt> attribute is
181  * present, the node value will be used as the label text. This attribute is
182  * also automatically populated if the nodes are specified as an array of
183  * primitives, such as strings or numbers.
184  *
185  * @type object
186  * @name pv.Layout.Network.Node.prototype.nodeValue
187  */
188 
189 /**
190  * @class Represents a link in a network layout. There is no explicit
191  * constructor; this class merely serves to document the attributes that are
192  * used on links in network layouts. For hierarchical layouts, this class is
193  * used to represent the parent-child links.
194  *
195  * @see pv.Layout.Network
196  * @name pv.Layout.Network.Link
197  */
198 
199 /**
200  * The link value, or weight; optional. If not specified (or not a number), the
201  * default value of 1 is used.
202  *
203  * @type number
204  * @name pv.Layout.Network.Link.prototype.linkValue
205  */
206 
207 /**
208  * The link's source node. If not set, this value will be derived from the
209  * <tt>source</tt> attribute index.
210  *
211  * @type pv.Layout.Network.Node
212  * @name pv.Layout.Network.Link.prototype.sourceNode
213  */
214 
215 /**
216  * The link's target node. If not set, this value will be derived from the
217  * <tt>target</tt> attribute index.
218  *
219  * @type pv.Layout.Network.Node
220  * @name pv.Layout.Network.Link.prototype.targetNode
221  */
222 
223 /**
224  * Alias for <tt>sourceNode</tt>, as expressed by the index of the source node.
225  * This attribute is not populated automatically, but may be used as a more
226  * convenient identification of the link's source, for example in a static JSON
227  * representation.
228  *
229  * @type number
230  * @name pv.Layout.Network.Link.prototype.source
231  */
232 
233 /**
234  * Alias for <tt>targetNode</tt>, as expressed by the index of the target node.
235  * This attribute is not populated automatically, but may be used as a more
236  * convenient identification of the link's target, for example in a static JSON
237  * representation.
238  *
239  * @type number
240  * @name pv.Layout.Network.Link.prototype.target
241  */
242 
243 /**
244  * Alias for <tt>linkValue</tt>. This attribute is not populated automatically,
245  * but may be used instead of the <tt>linkValue</tt> attribute when specifying
246  * links.
247  *
248  * @type number
249  * @name pv.Layout.Network.Link.prototype.value
250  */
251 
252 /** @private Transform nodes and links on cast. */
253 pv.Layout.Network.prototype = pv.extend(pv.Layout)
254     .property("nodes", function(v) {
255         return v.map(function(d, i) {
256             if (typeof d != "object") d = {nodeValue: d};
257             d.index = i;
258             d.linkDegree = 0;
259             return d;
260           });
261       })
262     .property("links", function(v) {
263         return v.map(function(d) {
264             if (isNaN(d.linkValue)) d.linkValue = isNaN(d.value) ? 1 : d.value;
265             return d;
266           });
267       });
268 
269 /**
270  * Resets the cache, such that changes to layout property definitions will be
271  * visible on subsequent render. Unlike normal marks (and normal layouts),
272  * properties associated with network layouts are not automatically re-evaluated
273  * on render; the properties are cached, and any expensive layout algorithms are
274  * only run after the layout is explicitly reset.
275  *
276  * @returns {pv.Layout.Network} this.
277  */
278 pv.Layout.Network.prototype.reset = function() {
279   this.$id = pv.id();
280   return this;
281 };
282 
283 /** @private Skip evaluating properties if cached. */
284 pv.Layout.Network.prototype.buildProperties = function(s, properties) {
285   if ((s.$id || 0) < this.$id) {
286     pv.Layout.prototype.buildProperties.call(this, s, properties);
287   }
288 };
289 
290 /** @private Compute link degrees; map source and target indexes to nodes. */
291 pv.Layout.Network.prototype.buildImplied = function(s) {
292   pv.Layout.prototype.buildImplied.call(this, s);
293   if (s.$id >= this.$id) return true;
294   s.$id = this.$id;
295   s.links.forEach(function(d) {
296       var v = d.linkValue;
297       (d.sourceNode || (d.sourceNode = s.nodes[d.source])).linkDegree += v;
298       (d.targetNode || (d.targetNode = s.nodes[d.target])).linkDegree += v;
299     });
300 };
301