1 /** 2 * Constructs a new mark with default properties. Marks, with the exception of 3 * the root panel, are not typically constructed directly; instead, they are 4 * added to a panel or an existing mark via {@link pv.Mark#add}. 5 * 6 * @class Represents a data-driven graphical mark. The <tt>Mark</tt> class is 7 * the base class for all graphical marks in Protovis; it does not provide any 8 * specific rendering functionality, but together with {@link Panel} establishes 9 * the core framework. 10 * 11 * <p>Concrete mark types include familiar visual elements such as bars, lines 12 * and labels. Although a bar mark may be used to construct a bar chart, marks 13 * know nothing about charts; it is only through their specification and 14 * composition that charts are produced. These building blocks permit many 15 * combinatorial possibilities. 16 * 17 * <p>Marks are associated with <b>data</b>: a mark is generated once per 18 * associated datum, mapping the datum to visual <b>properties</b> such as 19 * position and color. Thus, a single mark specification represents a set of 20 * visual elements that share the same data and visual encoding. The type of 21 * mark defines the names of properties and their meaning. A property may be 22 * static, ignoring the associated datum and returning a constant; or, it may be 23 * dynamic, derived from the associated datum or index. Such dynamic encodings 24 * can be specified succinctly using anonymous functions. Special properties 25 * called event handlers can be registered to add interactivity. 26 * 27 * <p>Protovis uses <b>inheritance</b> to simplify the specification of related 28 * marks: a new mark can be derived from an existing mark, inheriting its 29 * properties. The new mark can then override properties to specify new 30 * behavior, potentially in terms of the old behavior. In this way, the old mark 31 * serves as the <b>prototype</b> for the new mark. Most mark types share the 32 * same basic properties for consistency and to facilitate inheritance. 33 * 34 * <p>The prioritization of redundant properties is as follows:<ol> 35 * 36 * <li>If the <tt>width</tt> property is not specified (i.e., null), its value 37 * is the width of the parent panel, minus this mark's left and right margins; 38 * the left and right margins are zero if not specified. 39 * 40 * <li>Otherwise, if the <tt>right</tt> margin is not specified, its value is 41 * the width of the parent panel, minus this mark's width and left margin; the 42 * left margin is zero if not specified. 43 * 44 * <li>Otherwise, if the <tt>left</tt> property is not specified, its value is 45 * the width of the parent panel, minus this mark's width and the right margin. 46 * 47 * </ol>This prioritization is then duplicated for the <tt>height</tt>, 48 * <tt>bottom</tt> and <tt>top</tt> properties, respectively. 49 * 50 * <p>While most properties are <i>variable</i>, some mark types, such as lines 51 * and areas, generate a single visual element rather than a distinct visual 52 * element per datum. With these marks, some properties may be <b>fixed</b>. 53 * Fixed properties can vary per mark, but not <i>per datum</i>! These 54 * properties are evaluated solely for the first (0-index) datum, and typically 55 * are specified as a constant. However, it is valid to use a function if the 56 * property varies between panels or is dynamically generated. 57 * 58 * <p>See also the <a href="../../api/">Protovis guide</a>. 59 */ 60 pv.Mark = function() { 61 /* 62 * TYPE 0 constant defs 63 * TYPE 1 function defs 64 * TYPE 2 constant properties 65 * TYPE 3 function properties 66 * in order of evaluation! 67 */ 68 this.$properties = []; 69 this.$handlers = {}; 70 }; 71 72 /** @private Records which properties are defined on this mark type. */ 73 pv.Mark.prototype.properties = {}; 74 75 /** @private Records the cast function for each property. */ 76 pv.Mark.cast = {}; 77 78 /** 79 * @private Defines and registers a property method for the property with the 80 * given name. This method should be called on a mark class prototype to define 81 * each exposed property. (Note this refers to the JavaScript 82 * <tt>prototype</tt>, not the Protovis mark prototype, which is the {@link 83 * #proto} field.) 84 * 85 * <p>The created property method supports several modes of invocation: <ol> 86 * 87 * <li>If invoked with a <tt>Function</tt> argument, this function is evaluated 88 * for each associated datum. The return value of the function is used as the 89 * computed property value. The context of the function (<tt>this</tt>) is this 90 * mark. The arguments to the function are the associated data of this mark and 91 * any enclosing panels. For example, a linear encoding of numerical data to 92 * height is specified as 93 * 94 * <pre>m.height(function(d) d * 100);</pre> 95 * 96 * The expression <tt>d * 100</tt> will be evaluated for the height property of 97 * each mark instance. The return value of the property method (e.g., 98 * <tt>m.height</tt>) is this mark (<tt>m</tt>)).<p> 99 * 100 * <li>If invoked with a non-function argument, the property is treated as a 101 * constant. The return value of the property method (e.g., <tt>m.height</tt>) 102 * is this mark.<p> 103 * 104 * <li>If invoked with no arguments, the computed property value for the current 105 * mark instance in the scene graph is returned. This facilitates <i>property 106 * chaining</i>, where one mark's properties are defined in terms of another's. 107 * For example, to offset a mark's location from its prototype, you might say 108 * 109 * <pre>m.top(function() this.proto.top() + 10);</pre> 110 * 111 * Note that the index of the mark being evaluated (in the above example, 112 * <tt>this.proto</tt>) is inherited from the <tt>Mark</tt> class and set by 113 * this mark. So, if the fifth element's top property is being evaluated, the 114 * fifth instance of <tt>this.proto</tt> will similarly be queried for the value 115 * of its top property. If the mark being evaluated has a different number of 116 * instances, or its data is unrelated, the behavior of this method is 117 * undefined. In these cases it may be better to index the <tt>scene</tt> 118 * explicitly to specify the exact instance. 119 * 120 * </ol><p>Property names should follow standard JavaScript method naming 121 * conventions, using lowerCamel-style capitalization. 122 * 123 * <p>In addition to creating the property method, every property is registered 124 * in the {@link #properties} map on the <tt>prototype</tt>. Although this is an 125 * instance field, it is considered immutable and shared by all instances of a 126 * given mark type. The <tt>properties</tt> map can be queried to see if a mark 127 * type defines a particular property, such as width or height. 128 * 129 * @param {string} name the property name. 130 * @param {function} [cast] the cast function for this property. 131 */ 132 pv.Mark.prototype.property = function(name, cast) { 133 if (!this.hasOwnProperty("properties")) { 134 this.properties = pv.extend(this.properties); 135 } 136 this.properties[name] = true; 137 138 /* 139 * Define the setter-getter globally, since the default behavior should be the 140 * same for all properties, and since the Protovis inheritance chain is 141 * independent of the JavaScript inheritance chain. For example, anchors 142 * define a "name" property that is evaluated on derived marks, even though 143 * those marks don't normally have a name. 144 */ 145 pv.Mark.prototype.propertyMethod(name, false, pv.Mark.cast[name] = cast); 146 return this; 147 }; 148 149 /** 150 * @private Defines a setter-getter for the specified property. 151 * 152 * <p>If a cast function has been assigned to the specified property name, the 153 * property function is wrapped by the cast function, or, if a constant is 154 * specified, the constant is immediately cast. Note, however, that if the 155 * property value is null, the cast function is not invoked. 156 * 157 * @param {string} name the property name. 158 * @param {boolean} [def] whether is a property or a def. 159 * @param {function} [cast] the cast function for this property. 160 */ 161 pv.Mark.prototype.propertyMethod = function(name, def, cast) { 162 if (!cast) cast = pv.Mark.cast[name]; 163 this[name] = function(v) { 164 165 /* If this is a def, use it rather than property. */ 166 if (def && this.scene) { 167 var defs = this.scene.defs; 168 if (arguments.length) { 169 defs[name] = { 170 id: (v == null) ? 0 : pv.id(), 171 value: ((v != null) && cast) ? cast(v) : v 172 }; 173 return this; 174 } 175 return defs[name] ? defs[name].value : null; 176 } 177 178 /* If arguments are specified, set the property value. */ 179 if (arguments.length) { 180 var type = !def << 1 | (typeof v == "function"); 181 this.propertyValue(name, (type & 1 && cast) ? function() { 182 var x = v.apply(this, arguments); 183 return (x != null) ? cast(x) : null; 184 } : (((v != null) && cast) ? cast(v) : v)).type = type; 185 return this; 186 } 187 188 return this.instance()[name]; 189 }; 190 }; 191 192 /** @private Sets the value of the property <i>name</i> to <i>v</i>. */ 193 pv.Mark.prototype.propertyValue = function(name, v) { 194 var properties = this.$properties, p = {name: name, id: pv.id(), value: v}; 195 for (var i = 0; i < properties.length; i++) { 196 if (properties[i].name == name) { 197 properties.splice(i, 1); 198 break; 199 } 200 } 201 properties.push(p); 202 return p; 203 }; 204 205 /* Define all global properties. */ 206 pv.Mark.prototype 207 .property("data") 208 .property("visible", Boolean) 209 .property("left", Number) 210 .property("right", Number) 211 .property("top", Number) 212 .property("bottom", Number) 213 .property("cursor", String) 214 .property("title", String) 215 .property("reverse", Boolean) 216 .property("antialias", Boolean) 217 .property("events", String); 218 219 /** 220 * The mark type; a lower camelCase name. The type name controls rendering 221 * behavior, and unless the rendering engine is extended, must be one of the 222 * built-in concrete mark types: area, bar, dot, image, label, line, panel, 223 * rule, or wedge. 224 * 225 * @type string 226 * @name pv.Mark.prototype.type 227 */ 228 229 /** 230 * The mark prototype, possibly undefined, from which to inherit property 231 * functions. The mark prototype is not necessarily of the same type as this 232 * mark. Any properties defined on this mark will override properties inherited 233 * either from the prototype or from the type-specific defaults. 234 * 235 * @type pv.Mark 236 * @name pv.Mark.prototype.proto 237 */ 238 239 /** 240 * The enclosing parent panel. The parent panel is generally undefined only for 241 * the root panel; however, it is possible to create "offscreen" marks that are 242 * used only for inheritance purposes. 243 * 244 * @type pv.Panel 245 * @name pv.Mark.prototype.parent 246 */ 247 248 /** 249 * The child index. -1 if the enclosing parent panel is null; otherwise, the 250 * zero-based index of this mark into the parent panel's <tt>children</tt> array. 251 * 252 * @type number 253 */ 254 pv.Mark.prototype.childIndex = -1; 255 256 /** 257 * The mark index. The value of this field depends on which instance (i.e., 258 * which element of the data array) is currently being evaluated. During the 259 * build phase, the index is incremented over each datum; when handling events, 260 * the index is set to the instance that triggered the event. 261 * 262 * @type number 263 */ 264 pv.Mark.prototype.index = -1; 265 266 /** 267 * The current scale factor, based on any enclosing transforms. The current 268 * scale can be used to create scale-independent graphics. For example, to 269 * define a dot that has a radius of 10 irrespective of any zooming, say: 270 * 271 * <pre>dot.radius(function() 10 / this.scale)</pre> 272 * 273 * Note that the stroke width and font size are defined irrespective of scale 274 * (i.e., in screen space) already. Also note that when a transform is applied 275 * to a panel, the scale affects only the child marks, not the panel itself. 276 * 277 * @type number 278 * @see pv.Panel#transform 279 */ 280 pv.Mark.prototype.scale = 1; 281 282 /** 283 * @private The scene graph. The scene graph is an array of objects; each object 284 * (or "node") corresponds to an instance of this mark and an element in the 285 * data array. The scene graph can be traversed to lookup previously-evaluated 286 * properties. 287 * 288 * @name pv.Mark.prototype.scene 289 */ 290 291 /** 292 * The root parent panel. This may be undefined for "offscreen" marks that are 293 * created for inheritance purposes only. 294 * 295 * @type pv.Panel 296 * @name pv.Mark.prototype.root 297 */ 298 299 /** 300 * The data property; an array of objects. The size of the array determines the 301 * number of marks that will be instantiated; each element in the array will be 302 * passed to property functions to compute the property values. Typically, the 303 * data property is specified as a constant array, such as 304 * 305 * <pre>m.data([1, 2, 3, 4, 5]);</pre> 306 * 307 * However, it is perfectly acceptable to define the data property as a 308 * function. This function might compute the data dynamically, allowing 309 * different data to be used per enclosing panel. For instance, in the stacked 310 * area graph example (see {@link #scene}), the data function on the area mark 311 * dereferences each series. 312 * 313 * @type array 314 * @name pv.Mark.prototype.data 315 */ 316 317 /** 318 * The visible property; a boolean determining whether or not the mark instance 319 * is visible. If a mark instance is not visible, its other properties will not 320 * be evaluated. Similarly, for panels no child marks will be rendered. 321 * 322 * @type boolean 323 * @name pv.Mark.prototype.visible 324 */ 325 326 /** 327 * The left margin; the distance, in pixels, between the left edge of the 328 * enclosing panel and the left edge of this mark. Note that in some cases this 329 * property may be redundant with the right property, or with the conjunction of 330 * right and width. 331 * 332 * @type number 333 * @name pv.Mark.prototype.left 334 */ 335 336 /** 337 * The right margin; the distance, in pixels, between the right edge of the 338 * enclosing panel and the right edge of this mark. Note that in some cases this 339 * property may be redundant with the left property, or with the conjunction of 340 * left and width. 341 * 342 * @type number 343 * @name pv.Mark.prototype.right 344 */ 345 346 /** 347 * The top margin; the distance, in pixels, between the top edge of the 348 * enclosing panel and the top edge of this mark. Note that in some cases this 349 * property may be redundant with the bottom property, or with the conjunction 350 * of bottom and height. 351 * 352 * @type number 353 * @name pv.Mark.prototype.top 354 */ 355 356 /** 357 * The bottom margin; the distance, in pixels, between the bottom edge of the 358 * enclosing panel and the bottom edge of this mark. Note that in some cases 359 * this property may be redundant with the top property, or with the conjunction 360 * of top and height. 361 * 362 * @type number 363 * @name pv.Mark.prototype.bottom 364 */ 365 366 /** 367 * The cursor property; corresponds to the CSS cursor property. This is 368 * typically used in conjunction with event handlers to indicate interactivity. 369 * 370 * @type string 371 * @name pv.Mark.prototype.cursor 372 * @see <a href="http://www.w3.org/TR/CSS2/ui.html#propdef-cursor">CSS2 cursor</a> 373 */ 374 375 /** 376 * The title property; corresponds to the HTML/SVG title property, allowing the 377 * general of simple plain text tooltips. 378 * 379 * @type string 380 * @name pv.Mark.prototype.title 381 */ 382 383 /** 384 * The events property; corresponds to the SVG pointer-events property, 385 * specifying how the mark should participate in mouse events. The default value 386 * is "painted". Supported values are: 387 * 388 * <p>"painted": The given mark may receive events when the mouse is over a 389 * "painted" area. The painted areas are the interior (i.e., fill) of the mark 390 * if a 'fillStyle' is specified, and the perimeter (i.e., stroke) of the mark 391 * if a 'strokeStyle' is specified. 392 * 393 * <p>"all": The given mark may receive events when the mouse is over either the 394 * interior (i.e., fill) or the perimeter (i.e., stroke) of the mark, regardless 395 * of the specified fillStyle and strokeStyle. 396 * 397 * <p>"none": The given mark may not receive events. 398 * 399 * @type string 400 * @name pv.Mark.prototype.events 401 */ 402 403 /** 404 * The reverse property; a boolean determining whether marks are ordered from 405 * front-to-back or back-to-front. SVG does not support explicit z-ordering; 406 * shapes are rendered in the order they appear. Thus, by default, marks are 407 * rendered in data order. Setting the reverse property to false reverses the 408 * order in which they are rendered; however, the properties are still evaluated 409 * (i.e., built) in forward order. 410 * 411 * @type boolean 412 * @name pv.Mark.prototype.reverse 413 */ 414 415 /** 416 * Default properties for all mark types. By default, the data array is the 417 * parent data as a single-element array; if the data property is not specified, 418 * this causes each mark to be instantiated as a singleton with the parents 419 * datum. The visible property is true by default, and the reverse property is 420 * false. 421 * 422 * @type pv.Mark 423 */ 424 pv.Mark.prototype.defaults = new pv.Mark() 425 .data(function(d) { return [d]; }) 426 .visible(true) 427 .antialias(true) 428 .events("painted"); 429 430 /** 431 * Sets the prototype of this mark to the specified mark. Any properties not 432 * defined on this mark may be inherited from the specified prototype mark, or 433 * its prototype, and so on. The prototype mark need not be the same type of 434 * mark as this mark. (Note that for inheritance to be useful, properties with 435 * the same name on different mark types should have equivalent meaning.) 436 * 437 * @param {pv.Mark} proto the new prototype. 438 * @returns {pv.Mark} this mark. 439 * @see #add 440 */ 441 pv.Mark.prototype.extend = function(proto) { 442 this.proto = proto; 443 return this; 444 }; 445 446 /** 447 * Adds a new mark of the specified type to the enclosing parent panel, whilst 448 * simultaneously setting the prototype of the new mark to be this mark. 449 * 450 * @param {function} type the type of mark to add; a constructor, such as 451 * <tt>pv.Bar</tt>. 452 * @returns {pv.Mark} the new mark. 453 * @see #extend 454 */ 455 pv.Mark.prototype.add = function(type) { 456 return this.parent.add(type).extend(this); 457 }; 458 459 /** 460 * Defines a custom property on this mark. Custom properties are currently 461 * fixed, in that they are initialized once per mark set (i.e., per parent panel 462 * instance). Custom properties can be used to store local state for the mark, 463 * such as data needed by other properties (e.g., a custom scale) or interaction 464 * state. 465 * 466 * <p>WARNING We plan on changing this feature in a future release to define 467 * standard properties, as opposed to <i>fixed</i> properties that behave 468 * idiosyncratically within event handlers. Furthermore, we recommend storing 469 * state in an external data structure, rather than tying it to the 470 * visualization specification as with defs. 471 * 472 * @param {string} name the name of the local variable. 473 * @param {function} [v] an optional initializer; may be a constant or a 474 * function. 475 */ 476 pv.Mark.prototype.def = function(name, v) { 477 this.propertyMethod(name, true); 478 return this[name](arguments.length > 1 ? v : null); 479 }; 480 481 /** 482 * Returns an anchor with the specified name. All marks support the five 483 * standard anchor names:<ul> 484 * 485 * <li>top 486 * <li>left 487 * <li>center 488 * <li>bottom 489 * <li>right 490 * 491 * </ul>In addition to positioning properties (left, right, top bottom), the 492 * anchors support text rendering properties (text-align, text-baseline). Text is 493 * rendered to appear inside the mark by default. 494 * 495 * <p>To facilitate stacking, anchors are defined in terms of their opposite 496 * edge. For example, the top anchor defines the bottom property, such that the 497 * mark extends upwards; the bottom anchor instead defines the top property, 498 * such that the mark extends downwards. See also {@link pv.Layout.Stack}. 499 * 500 * <p>While anchor names are typically constants, the anchor name is a true 501 * property, which means you can specify a function to compute the anchor name 502 * dynamically. See the {@link pv.Anchor#name} property for details. 503 * 504 * @param {string} name the anchor name; either a string or a property function. 505 * @returns {pv.Anchor} the new anchor. 506 */ 507 pv.Mark.prototype.anchor = function(name) { 508 var target = this, scene; 509 510 /* Default anchor name. */ 511 if (!name) name = "center"; 512 513 /** @private Find the instances of target that match source. */ 514 function instances(source) { 515 var mark = target, index = []; 516 517 /* Mirrored descent. */ 518 while (!(scene = mark.scene)) { 519 source = source.parent; 520 index.push({index: source.index, childIndex: mark.childIndex}); 521 mark = mark.parent; 522 } 523 while (index.length) { 524 var i = index.pop(); 525 scene = scene[i.index].children[i.childIndex]; 526 } 527 528 /* 529 * When the anchor target is also an ancestor, as in the case of adding 530 * to a panel anchor, only generate one instance per panel. Also, set 531 * the margins to zero, since they are offset by the enclosing panel. 532 */ 533 if (target.hasOwnProperty("index")) { 534 var s = pv.extend(scene[target.index]); 535 s.right = s.top = s.left = s.bottom = 0; 536 return [s]; 537 } 538 return scene; 539 } 540 541 return new pv.Anchor(this) 542 .name(name) 543 .def("$mark.anchor", function() { 544 scene = this.scene.target = instances(this); 545 }) 546 .data(function() { 547 return scene.map(function(s) { return s.data; }); 548 }) 549 .visible(function() { 550 return scene[this.index].visible; 551 }) 552 .left(function() { 553 var s = scene[this.index], w = s.width || 0; 554 switch (this.name()) { 555 case "bottom": 556 case "top": 557 case "center": return s.left + w / 2; 558 case "left": return null; 559 } 560 return s.left + w; 561 }) 562 .top(function() { 563 var s = scene[this.index], h = s.height || 0; 564 switch (this.name()) { 565 case "left": 566 case "right": 567 case "center": return s.top + h / 2; 568 case "top": return null; 569 } 570 return s.top + h; 571 }) 572 .right(function() { 573 var s = scene[this.index]; 574 return this.name() == "left" ? s.right + (s.width || 0) : null; 575 }) 576 .bottom(function() { 577 var s = scene[this.index]; 578 return this.name() == "top" ? s.bottom + (s.height || 0) : null; 579 }) 580 .textAlign(function() { 581 switch (this.name()) { 582 case "bottom": 583 case "top": 584 case "center": return "center"; 585 case "right": return "right"; 586 } 587 return "left"; 588 }) 589 .textBaseline(function() { 590 switch (this.name()) { 591 case "right": 592 case "left": 593 case "center": return "middle"; 594 case "top": return "top"; 595 } 596 return "bottom"; 597 }); 598 }; 599 600 /** 601 * Returns the anchor target of this mark, if it is derived from an anchor; 602 * otherwise returns null. For example, if a label is derived from a bar anchor, 603 * 604 * <pre>bar.anchor("top").add(pv.Label);</pre> 605 * 606 * then property functions on the label can refer to the bar via the 607 * <tt>anchorTarget</tt> method. This method is also useful for mark types 608 * defining properties on custom anchors. 609 * 610 * @returns {pv.Mark} the anchor target of this mark; possibly null. 611 */ 612 pv.Mark.prototype.anchorTarget = function() { 613 return this.proto.anchorTarget(); 614 }; 615 616 /** 617 * Alias for setting the left, right, top and bottom properties simultaneously. 618 * 619 * @see #left 620 * @see #right 621 * @see #top 622 * @see #bottom 623 * @returns {pv.Mark} this. 624 */ 625 pv.Mark.prototype.margin = function(n) { 626 return this.left(n).right(n).top(n).bottom(n); 627 }; 628 629 /** 630 * @private Returns the current instance of this mark in the scene graph. This 631 * is typically equivalent to <tt>this.scene[this.index]</tt>, however if the 632 * scene or index is unset, the default instance of the mark is returned. If no 633 * default is set, the default is the last instance. Similarly, if the scene or 634 * index of the parent panel is unset, the default instance of this mark in the 635 * last instance of the enclosing panel is returned, and so on. 636 * 637 * @returns a node in the scene graph. 638 */ 639 pv.Mark.prototype.instance = function(defaultIndex) { 640 var scene = this.scene || this.parent.instance(-1).children[this.childIndex], 641 index = !arguments.length || this.hasOwnProperty("index") ? this.index : defaultIndex; 642 return scene[index < 0 ? scene.length - 1 : index]; 643 }; 644 645 /** 646 * @private Returns the first instance of this mark in the scene graph. This 647 * method can only be called when the mark is bound to the scene graph (for 648 * example, from an event handler, or within a property function). 649 * 650 * @returns a node in the scene graph. 651 */ 652 pv.Mark.prototype.first = function() { 653 return this.scene[0]; 654 }; 655 656 /** 657 * @private Returns the last instance of this mark in the scene graph. This 658 * method can only be called when the mark is bound to the scene graph (for 659 * example, from an event handler, or within a property function). In addition, 660 * note that mark instances are built sequentially, so the last instance of this 661 * mark may not yet be constructed. 662 * 663 * @returns a node in the scene graph. 664 */ 665 pv.Mark.prototype.last = function() { 666 return this.scene[this.scene.length - 1]; 667 }; 668 669 /** 670 * @private Returns the previous instance of this mark in the scene graph, or 671 * null if this is the first instance. 672 * 673 * @returns a node in the scene graph, or null. 674 */ 675 pv.Mark.prototype.sibling = function() { 676 return (this.index == 0) ? null : this.scene[this.index - 1]; 677 }; 678 679 /** 680 * @private Returns the current instance in the scene graph of this mark, in the 681 * previous instance of the enclosing parent panel. May return null if this 682 * instance could not be found. 683 * 684 * @returns a node in the scene graph, or null. 685 */ 686 pv.Mark.prototype.cousin = function() { 687 var p = this.parent, s = p && p.sibling(); 688 return (s && s.children) ? s.children[this.childIndex][this.index] : null; 689 }; 690 691 /** 692 * Renders this mark, including recursively rendering all child marks if this is 693 * a panel. This method finds all instances of this mark and renders them. This 694 * method descends recursively to the level of the mark to be rendered, finding 695 * all visible instances of the mark. After the marks are rendered, the scene 696 * and index attributes are removed from the mark to restore them to a clean 697 * state. 698 * 699 * <p>If an enclosing panel has an index property set (as is the case inside in 700 * an event handler), then only instances of this mark inside the given instance 701 * of the panel will be rendered; otherwise, all visible instances of the mark 702 * will be rendered. 703 */ 704 pv.Mark.prototype.render = function() { 705 var parent = this.parent, 706 stack = pv.Mark.stack; 707 708 /* For the first render, take it from the top. */ 709 if (parent && !this.root.scene) { 710 this.root.render(); 711 return; 712 } 713 714 /* Record the path to this mark. */ 715 var indexes = []; 716 for (var mark = this; mark.parent; mark = mark.parent) { 717 indexes.unshift(mark.childIndex); 718 } 719 720 /** @private */ 721 function render(mark, depth, scale) { 722 mark.scale = scale; 723 if (depth < indexes.length) { 724 stack.unshift(null); 725 if (mark.hasOwnProperty("index")) { 726 renderInstance(mark, depth, scale); 727 } else { 728 for (var i = 0, n = mark.scene.length; i < n; i++) { 729 mark.index = i; 730 renderInstance(mark, depth, scale); 731 } 732 delete mark.index; 733 } 734 stack.shift(); 735 } else { 736 mark.build(); 737 738 /* 739 * In the update phase, the scene is rendered by creating and updating 740 * elements and attributes in the SVG image. No properties are evaluated 741 * during the update phase; instead the values computed previously in the 742 * build phase are simply translated into SVG. The update phase is 743 * decoupled (see pv.Scene) to allow different rendering engines. 744 */ 745 pv.Scene.scale = scale; 746 pv.Scene.updateAll(mark.scene); 747 } 748 delete mark.scale; 749 } 750 751 /** 752 * @private Recursively renders the current instance of the specified mark. 753 * This is slightly tricky because `index` and `scene` properties may or may 754 * not already be set; if they are set, it means we are rendering only a 755 * specific instance; if they are unset, we are rendering all instances. 756 * Furthermore, we must preserve the original context of these properties when 757 * rendering completes. 758 * 759 * <p>Another tricky aspect is that the `scene` attribute should be set for 760 * any preceding children, so as to allow property chaining. This is 761 * consistent with first-pass rendering. 762 */ 763 function renderInstance(mark, depth, scale) { 764 var s = mark.scene[mark.index], i; 765 if (s.visible) { 766 var childIndex = indexes[depth], 767 child = mark.children[childIndex]; 768 769 /* Set preceding child scenes. */ 770 for (i = 0; i < childIndex; i++) { 771 mark.children[i].scene = s.children[i]; 772 } 773 774 /* Set current child scene, if necessary. */ 775 stack[0] = s.data; 776 if (child.scene) { 777 render(child, depth + 1, scale * s.transform.k); 778 } else { 779 child.scene = s.children[childIndex]; 780 render(child, depth + 1, scale * s.transform.k); 781 delete child.scene; 782 } 783 784 /* Clear preceding child scenes. */ 785 for (i = 0; i < childIndex; i++) { 786 delete mark.children[i].scene; 787 } 788 } 789 } 790 791 /* Bind this mark's property definitions. */ 792 this.bind(); 793 794 /* The render context is the first ancestor with an explicit index. */ 795 while (parent && !parent.hasOwnProperty("index")) parent = parent.parent; 796 797 /* Recursively render all instances of this mark. */ 798 this.context( 799 parent ? parent.scene : undefined, 800 parent ? parent.index : -1, 801 function() { render(this.root, 0, 1); }); 802 }; 803 804 /** @private Stores the current data stack. */ 805 pv.Mark.stack = []; 806 807 /** 808 * @private In the bind phase, inherited property definitions are cached so they 809 * do not need to be queried during build. 810 */ 811 pv.Mark.prototype.bind = function() { 812 var seen = {}, types = [[], [], [], []], data, visible; 813 814 /** Scans the proto chain for the specified mark. */ 815 function bind(mark) { 816 do { 817 var properties = mark.$properties; 818 for (var i = properties.length - 1; i >= 0 ; i--) { 819 var p = properties[i]; 820 if (!(p.name in seen)) { 821 seen[p.name] = p; 822 switch (p.name) { 823 case "data": data = p; break; 824 case "visible": visible = p; break; 825 default: types[p.type].push(p); break; 826 } 827 } 828 } 829 } while (mark = mark.proto); 830 } 831 832 /* Scan the proto chain for all defined properties. */ 833 bind(this); 834 bind(this.defaults); 835 types[1].reverse(); 836 types[3].reverse(); 837 838 /* Any undefined properties are null. */ 839 var mark = this; 840 do for (var name in mark.properties) { 841 if (!(name in seen)) { 842 types[2].push(seen[name] = {name: name, type: 2, value: null}); 843 } 844 } while (mark = mark.proto); 845 846 /* Define setter-getter for inherited defs. */ 847 var defs = types[0].concat(types[1]); 848 for (var i = 0; i < defs.length; i++) { 849 this.propertyMethod(defs[i].name, true); 850 } 851 852 /* Setup binds to evaluate constants before functions. */ 853 this.binds = { 854 properties: seen, 855 data: data, 856 defs: defs, 857 required: [visible], 858 optional: pv.blend(types) 859 }; 860 }; 861 862 /** 863 * @private Evaluates properties and computes implied properties. Properties are 864 * stored in the {@link #scene} array for each instance of this mark. 865 * 866 * <p>As marks are built recursively, the {@link #index} property is updated to 867 * match the current index into the data array for each mark. Note that the 868 * index property is only set for the mark currently being built and its 869 * enclosing parent panels. The index property for other marks is unset, but is 870 * inherited from the global <tt>Mark</tt> class prototype. This allows mark 871 * properties to refer to properties on other marks <i>in the same panel</i> 872 * conveniently; however, in general it is better to reference mark instances 873 * specifically through the scene graph rather than depending on the magical 874 * behavior of {@link #index}. 875 * 876 * <p>The root scene array has a special property, <tt>data</tt>, which stores 877 * the current data stack. The first element in this stack is the current datum, 878 * followed by the datum of the enclosing parent panel, and so on. The data 879 * stack should not be accessed directly; instead, property functions are passed 880 * the current data stack as arguments. 881 * 882 * <p>The evaluation of the <tt>data</tt> and <tt>visible</tt> properties is 883 * special. The <tt>data</tt> property is evaluated first; unlike the other 884 * properties, the data stack is from the parent panel, rather than the current 885 * mark, since the data is not defined until the data property is evaluated. 886 * The <tt>visisble</tt> property is subsequently evaluated for each instance; 887 * only if true will the {@link #buildInstance} method be called, evaluating 888 * other properties and recursively building the scene graph. 889 * 890 * <p>If this mark is being re-built, any old instances of this mark that no 891 * longer exist (because the new data array contains fewer elements) will be 892 * cleared using {@link #clearInstance}. 893 * 894 * @param parent the instance of the parent panel from the scene graph. 895 */ 896 pv.Mark.prototype.build = function() { 897 var scene = this.scene, stack = pv.Mark.stack; 898 if (!scene) { 899 scene = this.scene = []; 900 scene.mark = this; 901 scene.type = this.type; 902 scene.childIndex = this.childIndex; 903 if (this.parent) { 904 scene.parent = this.parent.scene; 905 scene.parentIndex = this.parent.index; 906 } 907 } 908 909 /* Evaluate defs. */ 910 if (this.binds.defs.length) { 911 var defs = scene.defs; 912 if (!defs) scene.defs = defs = {}; 913 for (var i = 0; i < this.binds.defs.length; i++) { 914 var p = this.binds.defs[i], d = defs[p.name]; 915 if (!d || (p.id > d.id)) { 916 defs[p.name] = { 917 id: 0, // this def will be re-evaluated on next build 918 value: (p.type & 1) ? p.value.apply(this, stack) : p.value 919 }; 920 } 921 } 922 } 923 924 /* Evaluate special data property. */ 925 var data = this.binds.data; 926 data = data.type & 1 ? data.value.apply(this, stack) : data.value; 927 928 /* Create, update and delete scene nodes. */ 929 stack.unshift(null); 930 scene.length = data.length; 931 for (var i = 0; i < data.length; i++) { 932 pv.Mark.prototype.index = this.index = i; 933 var s = scene[i]; 934 if (!s) scene[i] = s = {}; 935 s.data = stack[0] = data[i]; 936 this.buildInstance(s); 937 } 938 pv.Mark.prototype.index = -1; 939 delete this.index; 940 stack.shift(); 941 942 return this; 943 }; 944 945 /** 946 * @private Evaluates the specified array of properties for the specified 947 * instance <tt>s</tt> in the scene graph. 948 * 949 * @param s a node in the scene graph; the instance of the mark to build. 950 * @param properties an array of properties. 951 */ 952 pv.Mark.prototype.buildProperties = function(s, properties) { 953 for (var i = 0, n = properties.length; i < n; i++) { 954 var p = properties[i], v = p.value; // assume case 2 (constant) 955 switch (p.type) { 956 case 0: 957 case 1: v = this.scene.defs[p.name].value; break; 958 case 3: v = v.apply(this, pv.Mark.stack); break; 959 } 960 s[p.name] = v; 961 } 962 }; 963 964 /** 965 * @private Evaluates all of the properties for this mark for the specified 966 * instance <tt>s</tt> in the scene graph. The set of properties to evaluate is 967 * retrieved from the {@link #properties} array for this mark type (see {@link 968 * #type}). After these properties are evaluated, any <b>implied</b> properties 969 * may be computed by the mark and set on the scene graph; see 970 * {@link #buildImplied}. 971 * 972 * <p>For panels, this method recursively builds the scene graph for all child 973 * marks as well. In general, this method should not need to be overridden by 974 * concrete mark types. 975 * 976 * @param s a node in the scene graph; the instance of the mark to build. 977 */ 978 pv.Mark.prototype.buildInstance = function(s) { 979 this.buildProperties(s, this.binds.required); 980 if (s.visible) { 981 this.buildProperties(s, this.binds.optional); 982 this.buildImplied(s); 983 } 984 }; 985 986 /** 987 * @private Computes the implied properties for this mark for the specified 988 * instance <tt>s</tt> in the scene graph. Implied properties are those with 989 * dependencies on multiple other properties; for example, the width property 990 * may be implied if the left and right properties are set. This method can be 991 * overridden by concrete mark types to define new implied properties, if 992 * necessary. 993 * 994 * @param s a node in the scene graph; the instance of the mark to build. 995 */ 996 pv.Mark.prototype.buildImplied = function(s) { 997 var l = s.left; 998 var r = s.right; 999 var t = s.top; 1000 var b = s.bottom; 1001 1002 /* Assume width and height are zero if not supported by this mark type. */ 1003 var p = this.properties; 1004 var w = p.width ? s.width : 0; 1005 var h = p.height ? s.height : 0; 1006 1007 /* Compute implied width, right and left. */ 1008 var width = this.parent ? this.parent.width() : (w + l + r); 1009 if (w == null) { 1010 w = width - (r = r || 0) - (l = l || 0); 1011 } else if (r == null) { 1012 r = width - w - (l = l || 0); 1013 } else if (l == null) { 1014 l = width - w - (r = r || 0); 1015 } 1016 1017 /* Compute implied height, bottom and top. */ 1018 var height = this.parent ? this.parent.height() : (h + t + b); 1019 if (h == null) { 1020 h = height - (t = t || 0) - (b = b || 0); 1021 } else if (b == null) { 1022 b = height - h - (t = t || 0); 1023 } else if (t == null) { 1024 t = height - h - (b = b || 0); 1025 } 1026 1027 s.left = l; 1028 s.right = r; 1029 s.top = t; 1030 s.bottom = b; 1031 1032 /* Only set width and height if they are supported by this mark type. */ 1033 if (p.width) s.width = w; 1034 if (p.height) s.height = h; 1035 1036 /* Set any null colors to pv.Color.transparent. */ 1037 if (p.textStyle && !s.textStyle) s.textStyle = pv.Color.transparent; 1038 if (p.fillStyle && !s.fillStyle) s.fillStyle = pv.Color.transparent; 1039 if (p.strokeStyle && !s.strokeStyle) s.strokeStyle = pv.Color.transparent; 1040 }; 1041 1042 /** 1043 * Returns the current location of the mouse (cursor) relative to this mark's 1044 * parent. The <i>x</i> coordinate corresponds to the left margin, while the 1045 * <i>y</i> coordinate corresponds to the top margin. 1046 * 1047 * @returns {pv.Vector} the mouse location. 1048 */ 1049 pv.Mark.prototype.mouse = function() { 1050 1051 /* Compute xy-coordinates relative to the panel. */ 1052 var x = pv.event.pageX || 0, 1053 y = pv.event.pageY || 0, 1054 n = this.root.canvas(); 1055 do { 1056 x -= n.offsetLeft; 1057 y -= n.offsetTop; 1058 } while (n = n.offsetParent); 1059 1060 /* Compute the inverse transform of all enclosing panels. */ 1061 var t = pv.Transform.identity, 1062 p = this.properties.transform ? this : this.parent, 1063 pz = []; 1064 do { pz.push(p); } while (p = p.parent); 1065 while (p = pz.pop()) t = t.translate(p.left(), p.top()).times(p.transform()); 1066 t = t.invert(); 1067 1068 return pv.vector(x * t.k + t.x, y * t.k + t.y); 1069 }; 1070 1071 /** 1072 * Registers an event handler for the specified event type with this mark. When 1073 * an event of the specified type is triggered, the specified handler will be 1074 * invoked. The handler is invoked in a similar method to property functions: 1075 * the context is <tt>this</tt> mark instance, and the arguments are the full 1076 * data stack. Event handlers can use property methods to manipulate the display 1077 * properties of the mark: 1078 * 1079 * <pre>m.event("click", function() this.fillStyle("red"));</pre> 1080 * 1081 * Alternatively, the external data can be manipulated and the visualization 1082 * redrawn: 1083 * 1084 * <pre>m.event("click", function(d) { 1085 * data = all.filter(function(k) k.name == d); 1086 * vis.render(); 1087 * });</pre> 1088 * 1089 * The return value of the event handler determines which mark gets re-rendered. 1090 * Use defs ({@link #def}) to set temporary state from event handlers. 1091 * 1092 * <p>The complete set of event types is defined by SVG; see the reference 1093 * below. The set of supported event types is:<ul> 1094 * 1095 * <li>click 1096 * <li>mousedown 1097 * <li>mouseup 1098 * <li>mouseover 1099 * <li>mousemove 1100 * <li>mouseout 1101 * 1102 * </ul>Since Protovis does not specify any concept of focus, it does not 1103 * support key events; these should be handled outside the visualization using 1104 * standard JavaScript. In the future, support for interaction may be extended 1105 * to support additional event types, particularly those most relevant to 1106 * interactive visualization, such as selection. 1107 * 1108 * <p>TODO In the current implementation, event handlers are not inherited from 1109 * prototype marks. They must be defined explicitly on each interactive mark. In 1110 * addition, only one event handler for a given event type can be defined; when 1111 * specifying multiple event handlers for the same type, only the last one will 1112 * be used. 1113 * 1114 * @see <a href="http://www.w3.org/TR/SVGTiny12/interact.html#SVGEvents">SVG events</a> 1115 * @param {string} type the event type. 1116 * @param {function} handler the event handler. 1117 * @returns {pv.Mark} this. 1118 */ 1119 pv.Mark.prototype.event = function(type, handler) { 1120 this.$handlers[type] = pv.functor(handler); 1121 return this; 1122 }; 1123 1124 /** @private Evaluates the function <i>f</i> with the specified context. */ 1125 pv.Mark.prototype.context = function(scene, index, f) { 1126 var proto = pv.Mark.prototype, 1127 stack = pv.Mark.stack, 1128 oscene = pv.Mark.scene, 1129 oindex = proto.index; 1130 1131 /** @private Sets the context. */ 1132 function apply(scene, index) { 1133 pv.Mark.scene = scene; 1134 proto.index = index; 1135 if (!scene) return; 1136 1137 var that = scene.mark, 1138 mark = that, 1139 ancestors = []; 1140 1141 /* Set ancestors' scene and index; populate data stack. */ 1142 do { 1143 ancestors.push(mark); 1144 stack.push(scene[index].data); 1145 mark.index = index; 1146 mark.scene = scene; 1147 index = scene.parentIndex; 1148 scene = scene.parent; 1149 } while (mark = mark.parent); 1150 1151 /* Set ancestors' scale; requires top-down. */ 1152 for (var i = ancestors.length - 1, k = 1; i > 0; i--) { 1153 mark = ancestors[i]; 1154 mark.scale = k; 1155 k *= mark.scene[mark.index].transform.k; 1156 } 1157 1158 /* Set children's scene and scale. */ 1159 if (that.children) for (var i = 0, n = that.children.length; i < n; i++) { 1160 mark = that.children[i]; 1161 mark.scene = that.scene[that.index].children[i]; 1162 mark.scale = k; 1163 } 1164 } 1165 1166 /** @private Clears the context. */ 1167 function clear(scene, index) { 1168 if (!scene) return; 1169 var that = scene.mark, 1170 mark; 1171 1172 /* Reset children. */ 1173 if (that.children) for (var i = 0, n = that.children.length; i < n; i++) { 1174 mark = that.children[i]; 1175 delete mark.scene; 1176 delete mark.scale; 1177 } 1178 1179 /* Reset ancestors. */ 1180 mark = that; 1181 do { 1182 stack.pop(); 1183 if (mark.parent) { 1184 delete mark.scene; 1185 delete mark.scale; 1186 } 1187 delete mark.index; 1188 } while (mark = mark.parent); 1189 } 1190 1191 /* Context switch, invoke the function, then switch back. */ 1192 clear(oscene, oindex); 1193 apply(scene, index); 1194 try { 1195 f.apply(this, stack); 1196 } finally { 1197 clear(scene, index); 1198 apply(oscene, oindex); 1199 } 1200 }; 1201 1202 /** @private Execute the event listener, then re-render. */ 1203 pv.Mark.dispatch = function(type, scene, index) { 1204 var m = scene.mark, p = scene.parent, l = m.$handlers[type]; 1205 if (!l) return p && pv.Mark.dispatch(type, p, scene.parentIndex); 1206 m.context(scene, index, function() { 1207 m = l.apply(m, pv.Mark.stack); 1208 if (m && m.render) m.render(); 1209 }); 1210 return true; 1211 }; 1212