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