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 }; 70 71 /** @private TOOD */ 72 pv.Mark.prototype.properties = {}; 73 74 /** 75 * @private Defines and registers a property method for the property with the 76 * given name. This method should be called on a mark class prototype to define 77 * each exposed property. (Note this refers to the JavaScript 78 * <tt>prototype</tt>, not the Protovis mark prototype, which is the {@link 79 * #proto} field.) 80 * 81 * <p>The created property method supports several modes of invocation: <ol> 82 * 83 * <li>If invoked with a <tt>Function</tt> argument, this function is evaluated 84 * for each associated datum. The return value of the function is used as the 85 * computed property value. The context of the function (<tt>this</tt>) is this 86 * mark. The arguments to the function are the associated data of this mark and 87 * any enclosing panels. For example, a linear encoding of numerical data to 88 * height is specified as 89 * 90 * <pre>m.height(function(d) d * 100);</pre> 91 * 92 * The expression <tt>d * 100</tt> will be evaluated for the height property of 93 * each mark instance. The return value of the property method (e.g., 94 * <tt>m.height</tt>) is this mark (<tt>m</tt>)).<p> 95 * 96 * <li>If invoked with a non-function argument, the property is treated as a 97 * constant. The return value of the property method (e.g., <tt>m.height</tt>) 98 * is this mark.<p> 99 * 100 * <li>If invoked with no arguments, the computed property value for the current 101 * mark instance in the scene graph is returned. This facilitates <i>property 102 * chaining</i>, where one mark's properties are defined in terms of another's. 103 * For example, to offset a mark's location from its prototype, you might say 104 * 105 * <pre>m.top(function() this.proto.top() + 10);</pre> 106 * 107 * Note that the index of the mark being evaluated (in the above example, 108 * <tt>this.proto</tt>) is inherited from the <tt>Mark</tt> class and set by 109 * this mark. So, if the fifth element's top property is being evaluated, the 110 * fifth instance of <tt>this.proto</tt> will similarly be queried for the value 111 * of its top property. If the mark being evaluated has a different number of 112 * instances, or its data is unrelated, the behavior of this method is 113 * undefined. In these cases it may be better to index the <tt>scene</tt> 114 * explicitly to specify the exact instance. 115 * 116 * </ol><p>Property names should follow standard JavaScript method naming 117 * conventions, using lowerCamel-style capitalization. 118 * 119 * <p>In addition to creating the property method, every property is registered 120 * in the {@link #properties} map on the <tt>prototype</tt>. Although this is an 121 * instance field, it is considered immutable and shared by all instances of a 122 * given mark type. The <tt>properties</tt> map can be queried to see if a mark 123 * type defines a particular property, such as width or height. 124 * 125 * @param {string} name the property name. 126 */ 127 pv.Mark.prototype.property = function(name) { 128 if (!this.hasOwnProperty("properties")) { 129 this.properties = pv.extend(this.properties); 130 } 131 this.properties[name] = true; 132 133 /* 134 * Define the setter-getter globally, since the default behavior should be the 135 * same for all properties, and since the Protovis inheritance chain is 136 * independent of the JavaScript inheritance chain. For example, anchors 137 * define a "name" property that is evaluated on derived marks, even though 138 * those marks don't normally have a name. 139 */ 140 pv.Mark.prototype[name] = function(v) { 141 if (arguments.length) { 142 this.$properties.push({ 143 name: name, 144 type: (typeof v == "function") ? 3 : 2, 145 value: v 146 }); 147 return this; 148 } 149 return this.scene[this.index][name]; 150 }; 151 152 return this; 153 }; 154 155 /* Define all global properties. */ 156 pv.Mark.prototype 157 .property("data") 158 .property("visible") 159 .property("left") 160 .property("right") 161 .property("top") 162 .property("bottom") 163 .property("cursor") 164 .property("title") 165 .property("reverse"); 166 167 /** 168 * The mark type; a lower camelCase name. The type name controls rendering 169 * behavior, and unless the rendering engine is extended, must be one of the 170 * built-in concrete mark types: area, bar, dot, image, label, line, panel, 171 * rule, or wedge. 172 * 173 * @type string 174 * @name pv.Mark.prototype.type 175 */ 176 177 /** 178 * The mark prototype, possibly undefined, from which to inherit property 179 * functions. The mark prototype is not necessarily of the same type as this 180 * mark. Any properties defined on this mark will override properties inherited 181 * either from the prototype or from the type-specific defaults. 182 * 183 * @type pv.Mark 184 * @name pv.Mark.prototype.proto 185 */ 186 187 /** 188 * The enclosing parent panel. The parent panel is generally undefined only for 189 * the root panel; however, it is possible to create "offscreen" marks that are 190 * used only for inheritance purposes. 191 * 192 * @type pv.Panel 193 * @name pv.Mark.prototype.parent 194 */ 195 196 /** 197 * The child index. -1 if the enclosing parent panel is null; otherwise, the 198 * zero-based index of this mark into the parent panel's <tt>children</tt> array. 199 * 200 * @type number 201 */ 202 pv.Mark.prototype.childIndex = -1; 203 204 /** 205 * The mark index. The value of this field depends on which instance (i.e., 206 * which element of the data array) is currently being evaluated. During the 207 * build phase, the index is incremented over each datum; when handling events, 208 * the index is set to the instance that triggered the event. 209 * 210 * @type number 211 */ 212 pv.Mark.prototype.index = -1; 213 214 /** 215 * The scene graph. The scene graph is an array of objects; each object (or 216 * "node") corresponds to an instance of this mark and an element in the data 217 * array. The scene graph can be traversed to lookup previously-evaluated 218 * properties. 219 * 220 * <p>For instance, consider a stacked area chart. The bottom property of the 221 * area can be defined using the <i>cousin</i> instance, which is the current 222 * area instance in the previous instantiation of the parent panel. In this 223 * sample code, 224 * 225 * <pre>new pv.Panel() 226 * .width(150).height(150) 227 * .add(pv.Panel) 228 * .data([[1, 1.2, 1.7, 1.5, 1.7], 229 * [.5, 1, .8, 1.1, 1.3], 230 * [.2, .5, .8, .9, 1]]) 231 * .add(pv.Area) 232 * .data(function(d) d) 233 * .bottom(function() { 234 * var c = this.cousin(); 235 * return c ? (c.bottom + c.height) : 0; 236 * }) 237 * .height(function(d) d * 40) 238 * .left(function() this.index * 35) 239 * .root.render();</pre> 240 * 241 * the bottom property is computed based on the upper edge of the corresponding 242 * datum in the previous series. The area's parent panel is instantiated once 243 * per series, so the cousin refers to the previous (below) area mark. (Note 244 * that the position of the upper edge is not the same as the top property, 245 * which refers to the top margin: the distance from the top edge of the panel 246 * to the top edge of the mark.) 247 * 248 * @see #first 249 * @see #last 250 * @see #sibling 251 * @see #cousin 252 * @name pv.Mark.prototype.scene 253 */ 254 255 /** 256 * The root parent panel. This may be undefined for "offscreen" marks that are 257 * created for inheritance purposes only. 258 * 259 * @type pv.Panel 260 * @name pv.Mark.prototype.root 261 */ 262 263 /** 264 * The data property; an array of objects. The size of the array determines the 265 * number of marks that will be instantiated; each element in the array will be 266 * passed to property functions to compute the property values. Typically, the 267 * data property is specified as a constant array, such as 268 * 269 * <pre>m.data([1, 2, 3, 4, 5]);</pre> 270 * 271 * However, it is perfectly acceptable to define the data property as a 272 * function. This function might compute the data dynamically, allowing 273 * different data to be used per enclosing panel. For instance, in the stacked 274 * area graph example (see {@link #scene}), the data function on the area mark 275 * dereferences each series. 276 * 277 * @type array 278 * @name pv.Mark.prototype.data 279 */ 280 281 /** 282 * The visible property; a boolean determining whether or not the mark instance 283 * is visible. If a mark instance is not visible, its other properties will not 284 * be evaluated. Similarly, for panels no child marks will be rendered. 285 * 286 * @type boolean 287 * @name pv.Mark.prototype.visible 288 */ 289 290 /** 291 * The left margin; the distance, in pixels, between the left edge of the 292 * enclosing panel and the left edge of this mark. Note that in some cases this 293 * property may be redundant with the right property, or with the conjunction of 294 * right and width. 295 * 296 * @type number 297 * @name pv.Mark.prototype.left 298 */ 299 300 /** 301 * The right margin; the distance, in pixels, between the right edge of the 302 * enclosing panel and the right edge of this mark. Note that in some cases this 303 * property may be redundant with the left property, or with the conjunction of 304 * left and width. 305 * 306 * @type number 307 * @name pv.Mark.prototype.right 308 */ 309 310 /** 311 * The top margin; the distance, in pixels, between the top edge of the 312 * enclosing panel and the top edge of this mark. Note that in some cases this 313 * property may be redundant with the bottom property, or with the conjunction 314 * of bottom and height. 315 * 316 * @type number 317 * @name pv.Mark.prototype.top 318 */ 319 320 /** 321 * The bottom margin; the distance, in pixels, between the bottom edge of the 322 * enclosing panel and the bottom edge of this mark. Note that in some cases 323 * this property may be redundant with the top property, or with the conjunction 324 * of top and height. 325 * 326 * @type number 327 * @name pv.Mark.prototype.bottom 328 */ 329 330 /** 331 * The cursor property; corresponds to the CSS cursor property. This is 332 * typically used in conjunction with event handlers to indicate interactivity. 333 * 334 * @type string 335 * @name pv.Mark.prototype.cursor 336 * @see <a href="http://www.w3.org/TR/CSS2/ui.html#propdef-cursor">CSS2 cursor</a> 337 */ 338 339 /** 340 * The title property; corresponds to the HTML/SVG title property, allowing the 341 * general of simple plain text tooltips. 342 * 343 * @type string 344 * @name pv.Mark.prototype.title 345 */ 346 347 /** 348 * The reverse property; a boolean determining whether marks are ordered from 349 * front-to-back or back-to-front. SVG does not support explicit z-ordering; 350 * shapes are rendered in the order they appear. Thus, by default, marks are 351 * rendered in data order. Setting the reverse property to false reverses the 352 * order in which they are rendered; however, the properties are still evaluated 353 * (i.e., built) in forward order. 354 * 355 * @type boolean 356 * @name pv.Mark.prototype.reverse 357 */ 358 359 /** 360 * Default properties for all mark types. By default, the data array is the 361 * parent data as a single-element array; if the data property is not specified, 362 * this causes each mark to be instantiated as a singleton with the parents 363 * datum. The visible property is true by default, and the reverse property is 364 * false. 365 * 366 * @type pv.Mark 367 */ 368 pv.Mark.prototype.defaults = new pv.Mark() 369 .data(function(d) { return [d]; }) 370 .visible(true) 371 .reverse(false) 372 .cursor("") 373 .title(""); 374 375 /* Private categorical colors for default fill & stroke styles. */ 376 var defaultFillStyle = pv.Colors.category20().by(pv.parent), 377 defaultStrokeStyle = pv.Colors.category10().by(pv.parent); 378 379 /** 380 * Sets the prototype of this mark to the specified mark. Any properties not 381 * defined on this mark may be inherited from the specified prototype mark, or 382 * its prototype, and so on. The prototype mark need not be the same type of 383 * mark as this mark. (Note that for inheritance to be useful, properties with 384 * the same name on different mark types should have equivalent meaning.) 385 * 386 * @param {pv.Mark} proto the new prototype. 387 * @return {pv.Mark} this mark. 388 * @see #add 389 */ 390 pv.Mark.prototype.extend = function(proto) { 391 this.proto = proto; 392 return this; 393 }; 394 395 /** 396 * Adds a new mark of the specified type to the enclosing parent panel, whilst 397 * simultaneously setting the prototype of the new mark to be this mark. 398 * 399 * @param {function} type the type of mark to add; a constructor, such as 400 * <tt>pv.Bar</tt>. 401 * @return {pv.Mark} the new mark. 402 * @see #extend 403 */ 404 pv.Mark.prototype.add = function(type) { 405 return this.parent.add(type).extend(this); 406 }; 407 408 /** 409 * Defines a local variable on this mark. Local variables are initialized once 410 * per mark (i.e., per parent panel instance), and can be used to store local 411 * state for the mark. Here are a few reasons you might want to use 412 * <tt>def</tt>: 413 * 414 * <p>1. To store local state. For example, say you were visualizing employment 415 * statistics, and your root panel had an array of occupations. In a child 416 * panel, you might want to initialize a local scale, and reference it from a 417 * property function: 418 * 419 * <pre>.def("y", function(d) pv.Scale.linear(0, pv.max(d.values)).range(0, h)) 420 * .height(function(d) this.y()(d))</pre> 421 * 422 * In this example, <tt>this.y()</tt> returns the defined local scale. We then 423 * invoke the scale function, passing in the datum, to compute the height. Note 424 * that defs are similar to fixed properties: they are only evaluated once per 425 * parent panel, and <tt>this.y()</tt> returns a function, rather than 426 * automatically evaluating this function as a property. 427 * 428 * <p>2. To store temporary state for interaction. Say you have an array of 429 * bars, and you want to color the bar differently if the mouse is over it. Use 430 * <tt>def</tt> to define a local variable, and event handlers to override this 431 * variable interactively: 432 * 433 * <pre>.def("i", -1) 434 * .event("mouseover", function() this.i(this.index)) 435 * .event("mouseout", function() this.i(-1)) 436 * .fillStyle(function() this.i() == this.index ? "red" : "blue")</pre> 437 * 438 * Notice that <tt>this.i()</tt> can be used both to set the value of <i>i</i> 439 * (when an argument is specified), and to get the value of <i>i</i> (when no 440 * arguments are specified). In this way, it's like other property methods. 441 * 442 * <p>3. To specify fixed properties efficiently. Sometimes, the value of a 443 * property may be locally a constant, but dependent on parent panel data which 444 * is variable. In this scenario, you can use <tt>def</tt> to define a property; 445 * it will only get computed once per mark, rather than once per datum. 446 * 447 * @param {string} name the name of the local variable. 448 * @param {function} [value] an optional initializer; may be a constant or a 449 * function. 450 */ 451 pv.Mark.prototype.def = function(name, value) { 452 this.$properties.push({ 453 name: name, 454 type: (typeof value == "function") ? 1 : 0, 455 value: value 456 }); 457 return this; 458 }; 459 460 /** 461 * Returns an anchor with the specified name. While anchor names are typically 462 * constants, the anchor name is a true property, which means you can specify a 463 * function to compute the anchor name dynamically. See the 464 * {@link pv.Anchor#name} property for details. 465 * 466 * @param {string} name the anchor name; either a string or a property function. 467 * @returns {pv.Anchor} the new anchor. 468 */ 469 pv.Mark.prototype.anchor = function(name) { 470 var anchor = new pv.Anchor().extend(this).name(name); 471 anchor.parent = this.parent; 472 return anchor; 473 }; 474 475 /** 476 * Returns the anchor target of this mark, if it is derived from an anchor; 477 * otherwise returns null. For example, if a label is derived from a bar anchor, 478 * 479 * <pre>bar.anchor("top").add(pv.Label);</pre> 480 * 481 * then property functions on the label can refer to the bar via the 482 * <tt>anchorTarget</tt> method. This method is also useful for mark types 483 * defining properties on custom anchors. 484 * 485 * @returns {pv.Mark} the anchor target of this mark; possibly null. 486 */ 487 pv.Mark.prototype.anchorTarget = function() { 488 var target = this; 489 while (!(target instanceof pv.Anchor)) { 490 target = target.proto; 491 if (!target) return null; 492 } 493 return target.proto; 494 }; 495 496 /** 497 * Returns the first instance of this mark in the scene graph. This method can 498 * only be called when the mark is bound to the scene graph (for example, from 499 * an event handler, or within a property function). 500 * 501 * @returns a node in the scene graph. 502 */ 503 pv.Mark.prototype.first = function() { 504 return this.scene[0]; 505 }; 506 507 /** 508 * Returns the last instance of this mark in the scene graph. This method can 509 * only be called when the mark is bound to the scene graph (for example, from 510 * an event handler, or within a property function). In addition, note that mark 511 * instances are built sequentially, so the last instance of this mark may not 512 * yet be constructed. 513 * 514 * @returns a node in the scene graph. 515 */ 516 pv.Mark.prototype.last = function() { 517 return this.scene[this.scene.length - 1]; 518 }; 519 520 /** 521 * Returns the previous instance of this mark in the scene graph, or null if 522 * this is the first instance. 523 * 524 * @returns a node in the scene graph, or null. 525 */ 526 pv.Mark.prototype.sibling = function() { 527 return (this.index == 0) ? null : this.scene[this.index - 1]; 528 }; 529 530 /** 531 * Returns the current instance in the scene graph of this mark, in the previous 532 * instance of the enclosing parent panel. May return null if this instance 533 * could not be found. See the {@link pv.Layout.stack} function for an example 534 * property function using cousin. 535 * 536 * @see pv.Layout.stack 537 * @returns a node in the scene graph, or null. 538 */ 539 pv.Mark.prototype.cousin = function() { 540 var p = this.parent, s = p && p.sibling(); 541 return (s && s.children) ? s.children[this.childIndex][this.index] : null; 542 }; 543 544 /** 545 * Renders this mark, including recursively rendering all child marks if this is 546 * a panel. 547 */ 548 pv.Mark.prototype.render = function() { 549 /* 550 * Rendering consists of three phases: bind, build and update. The update 551 * phase is decoupled to allow different rendering engines. 552 * 553 * In the bind phase, inherited property definitions are cached so they do not 554 * need to be queried during build. In the build phase, properties are 555 * evaluated, and the scene graph is generated. In the update phase, the scene 556 * is rendered by creating and updating elements and attributes in the SVG 557 * image. No properties are evaluated during the update phase; instead the 558 * values computed previously in the build phase are simply translated into 559 * SVG. 560 */ 561 this.bind(); 562 this.build(); 563 pv.Scene.updateAll(this.scene); 564 }; 565 566 /** @private Computes the root data stack for the specified mark. */ 567 function argv(mark) { 568 var stack = []; 569 while (mark) { 570 stack.push(mark.scene[mark.index].data); 571 mark = mark.parent; 572 } 573 return stack; 574 } 575 576 /** @private TODO */ 577 pv.Mark.prototype.bind = function() { 578 var seen = {}, types = [[], [], [], []], data, visible; 579 580 /** TODO */ 581 function bind(mark) { 582 do { 583 var properties = mark.$properties; 584 for (var i = properties.length - 1; i >= 0 ; i--) { 585 var p = properties[i]; 586 if (!(p.name in seen)) { 587 seen[p.name] = 1; 588 switch (p.name) { 589 case "data": data = p; break; 590 case "visible": visible = p; break; 591 default: types[p.type].push(p); break; 592 } 593 } 594 } 595 } while (mark = mark.proto); 596 } 597 598 /** TODO */ 599 function def(name) { 600 return function(v) { 601 var defs = this.scene.defs; 602 if (arguments.length) { 603 if (v == undefined) { 604 delete defs.locked[name]; 605 } else { 606 defs.locked[name] = true; 607 } 608 defs.values[name] = v; 609 return this; 610 } else { 611 return defs.values[name]; 612 } 613 }; 614 } 615 616 /* Scan the proto chain for all defined properties. */ 617 bind(this); 618 bind(this.defaults); 619 types[1].reverse(); 620 types[3].reverse(); 621 622 /* Any undefined properties are null. */ 623 var mark = this; 624 do for (var name in mark.properties) { 625 if (!(name in seen)) { 626 seen[name] = 1; 627 types[2].push({name: name, type: 2, value: null}); 628 } 629 } while (mark = mark.proto); 630 631 /* Define setter-getter for inherited defs. */ 632 var defs = types[0].concat(types[1]); 633 for (var i = 0; i < defs.length; i++) { 634 var d = defs[i]; 635 this[d.name] = def(d.name); 636 } 637 638 /* Setup binds to evaluate constants before functions. */ 639 this.binds = { 640 data: data, 641 visible: visible, 642 defs: defs, 643 properties: pv.blend(types) 644 }; 645 }; 646 647 /** 648 * @private Evaluates properties and computes implied properties. Properties are 649 * stored in the {@link #scene} array for each instance of this mark. 650 * 651 * <p>As marks are built recursively, the {@link #index} property is updated to 652 * match the current index into the data array for each mark. Note that the 653 * index property is only set for the mark currently being built and its 654 * enclosing parent panels. The index property for other marks is unset, but is 655 * inherited from the global <tt>Mark</tt> class prototype. This allows mark 656 * properties to refer to properties on other marks <i>in the same panel</i> 657 * conveniently; however, in general it is better to reference mark instances 658 * specifically through the scene graph rather than depending on the magical 659 * behavior of {@link #index}. 660 * 661 * <p>The root scene array has a special property, <tt>data</tt>, which stores 662 * the current data stack. The first element in this stack is the current datum, 663 * followed by the datum of the enclosing parent panel, and so on. The data 664 * stack should not be accessed directly; instead, property functions are passed 665 * the current data stack as arguments. 666 * 667 * <p>The evaluation of the <tt>data</tt> and <tt>visible</tt> properties is 668 * special. The <tt>data</tt> property is evaluated first; unlike the other 669 * properties, the data stack is from the parent panel, rather than the current 670 * mark, since the data is not defined until the data property is evaluated. 671 * The <tt>visisble</tt> property is subsequently evaluated for each instance; 672 * only if true will the {@link #buildInstance} method be called, evaluating 673 * other properties and recursively building the scene graph. 674 * 675 * <p>If this mark is being re-built, any old instances of this mark that no 676 * longer exist (because the new data array contains fewer elements) will be 677 * cleared using {@link #clearInstance}. 678 * 679 * @param parent the instance of the parent panel from the scene graph. 680 */ 681 pv.Mark.prototype.build = function() { 682 var scene = this.scene; 683 if (!scene) { 684 scene = this.scene = []; 685 scene.mark = this; 686 scene.type = this.type; 687 scene.childIndex = this.childIndex; 688 if (this.parent) { 689 scene.parent = this.parent.scene; 690 scene.parentIndex = this.parent.index; 691 } 692 } 693 694 /* Set the data stack. */ 695 var stack = this.root.scene.data; 696 if (!stack) this.root.scene.data = stack = argv(this.parent); 697 698 /* Evaluate defs. */ 699 if (this.binds.defs.length) { 700 var defs = scene.defs; 701 if (!defs) scene.defs = defs = {values: {}, locked: {}}; 702 for (var i = 0; i < this.binds.defs.length; i++) { 703 var d = this.binds.defs[i]; 704 if (!(d.name in defs.locked)) { 705 var v = d.value; 706 if (d.type == 1) { 707 property = d.name; 708 v = v.apply(this, stack); 709 } 710 defs.values[d.name] = v; 711 } 712 } 713 } 714 715 /* Evaluate special data property. */ 716 var data = this.binds.data; 717 switch (data.type) { 718 case 0: case 1: data = defs.values.data; break; 719 case 2: data = data.value; break; 720 case 3: { 721 property = "data"; 722 data = data.value.apply(this, stack); 723 break; 724 } 725 } 726 727 /* Create, update and delete scene nodes. */ 728 stack.unshift(null); 729 scene.length = data.length; 730 for (var i = 0; i < data.length; i++) { 731 pv.Mark.prototype.index = this.index = i; 732 var s = scene[i]; 733 if (!s) scene[i] = s = {}; 734 s.data = stack[0] = data[i]; 735 736 /* Evaluate special visible property. */ 737 var visible = this.binds.visible; 738 switch (visible.type) { 739 case 0: case 1: visible = defs.values.visible; break; 740 case 2: visible = visible.value; break; 741 case 3: { 742 property = "visible"; 743 visible = visible.value.apply(this, stack); 744 break; 745 } 746 } 747 748 if (s.visible = visible) this.buildInstance(s); 749 } 750 stack.shift(); 751 delete this.index; 752 pv.Mark.prototype.index = -1; 753 if (!this.parent) scene.data = null; 754 755 return this; 756 }; 757 758 /** 759 * @private Evaluates the specified array of properties for the specified 760 * instance <tt>s</tt> in the scene graph. 761 * 762 * @param s a node in the scene graph; the instance of the mark to build. 763 * @param properties an array of properties. 764 */ 765 pv.Mark.prototype.buildProperties = function(s, properties) { 766 for (var i = 0, n = properties.length; i < n; i++) { 767 var p = properties[i], v = p.value; 768 switch (p.type) { 769 case 0: case 1: v = this.scene.defs.values[p.name]; break; 770 case 3: { 771 property = p.name; 772 v = v.apply(this, this.root.scene.data); 773 break; 774 } 775 } 776 s[p.name] = v; 777 } 778 }; 779 780 /** 781 * @private Evaluates all of the properties for this mark for the specified 782 * instance <tt>s</tt> in the scene graph. The set of properties to evaluate is 783 * retrieved from the {@link #properties} array for this mark type (see {@link 784 * #type}). After these properties are evaluated, any <b>implied</b> properties 785 * may be computed by the mark and set on the scene graph; see 786 * {@link #buildImplied}. 787 * 788 * <p>For panels, this method recursively builds the scene graph for all child 789 * marks as well. In general, this method should not need to be overridden by 790 * concrete mark types. 791 * 792 * @param s a node in the scene graph; the instance of the mark to build. 793 */ 794 pv.Mark.prototype.buildInstance = function(s) { 795 this.buildProperties(s, this.binds.properties); 796 this.buildImplied(s); 797 }; 798 799 /** 800 * @private Computes the implied properties for this mark for the specified 801 * instance <tt>s</tt> in the scene graph. Implied properties are those with 802 * dependencies on multiple other properties; for example, the width property 803 * may be implied if the left and right properties are set. This method can be 804 * overridden by concrete mark types to define new implied properties, if 805 * necessary. 806 * 807 * @param s a node in the scene graph; the instance of the mark to build. 808 */ 809 pv.Mark.prototype.buildImplied = function(s) { 810 var l = s.left; 811 var r = s.right; 812 var t = s.top; 813 var b = s.bottom; 814 815 /* Assume width and height are zero if not supported by this mark type. */ 816 var p = this.properties; 817 var w = p.width ? s.width : 0; 818 var h = p.height ? s.height : 0; 819 820 /* Compute implied width, right and left. */ 821 var width = this.parent ? this.parent.width() : (w + l + r); 822 if (w == null) { 823 w = width - (r = r || 0) - (l = l || 0); 824 } else if (r == null) { 825 r = width - w - (l = l || 0); 826 } else if (l == null) { 827 l = width - w - (r = r || 0); 828 } 829 830 /* Compute implied height, bottom and top. */ 831 var height = this.parent ? this.parent.height() : (h + t + b); 832 if (h == null) { 833 h = height - (t = t || 0) - (b = b || 0); 834 } else if (b == null) { 835 b = height - h - (t = t || 0); 836 } else if (t == null) { 837 t = height - h - (b = b || 0); 838 } 839 840 s.left = l; 841 s.right = r; 842 s.top = t; 843 s.bottom = b; 844 845 /* Only set width and height if they are supported by this mark type. */ 846 if (p.width) s.width = w; 847 if (p.height) s.height = h; 848 }; 849 850 /** 851 * @private The name of the property being evaluated, for so-called "smart" 852 * functions that change behavior depending on which property is being 853 * evaluated. This functionality is somewhat magical, so for now, this feature 854 * is not exposed outside the library. 855 * 856 * @type string 857 */ 858 var property; 859 860 /** @private The current mouse location. */ 861 var pageX = 0, pageY = 0; 862 pv.listen(window, "mousemove", function(e) { pageX = e.pageX; pageY = e.pageY; }); 863 864 /** 865 * Returns the current location of the mouse (cursor) relative to this mark's 866 * parent. The <i>x</i> coordinate corresponds to the left margin, while the 867 * <i>y</i> coordinate corresponds to the top margin. 868 * 869 * @returns {pv.Vector} the mouse location. 870 */ 871 pv.Mark.prototype.mouse = function() { 872 var x = 0, y = 0, mark = (this instanceof pv.Panel) ? this : this.parent; 873 do { 874 x += mark.left(); 875 y += mark.top(); 876 } while (mark = mark.parent); 877 var node = this.root.canvas(); 878 do { 879 x += node.offsetLeft; 880 y += node.offsetTop; 881 } while (node = node.offsetParent); 882 return pv.vector(pageX - x, pageY - y); 883 }; 884 885 /** 886 * Registers an event handler for the specified event type with this mark. When 887 * an event of the specified type is triggered, the specified handler will be 888 * invoked. The handler is invoked in a similar method to property functions: 889 * the context is <tt>this</tt> mark instance, and the arguments are the full 890 * data stack. Event handlers can use property methods to manipulate the display 891 * properties of the mark: 892 * 893 * <pre>m.event("click", function() this.fillStyle("red"));</pre> 894 * 895 * Alternatively, the external data can be manipulated and the visualization 896 * redrawn: 897 * 898 * <pre>m.event("click", function(d) { 899 * data = all.filter(function(k) k.name == d); 900 * vis.render(); 901 * });</pre> 902 * 903 * The return value of the event handler determines which mark gets re-rendered. 904 * Use defs ({@link #def}) to set temporary state from event handlers. 905 * 906 * <p>The complete set of event types is defined by SVG; see the reference 907 * below. The set of supported event types is:<ul> 908 * 909 * <li>click 910 * <li>mousedown 911 * <li>mouseup 912 * <li>mouseover 913 * <li>mousemove 914 * <li>mouseout 915 * 916 * </ul>Since Protovis does not specify any concept of focus, it does not 917 * support key events; these should be handled outside the visualization using 918 * standard JavaScript. In the future, support for interaction may be extended 919 * to support additional event types, particularly those most relevant to 920 * interactive visualization, such as selection. 921 * 922 * <p>TODO In the current implementation, event handlers are not inherited from 923 * prototype marks. They must be defined explicitly on each interactive mark. In 924 * addition, only one event handler for a given event type can be defined; when 925 * specifying multiple event handlers for the same type, only the last one will 926 * be used. 927 * 928 * @see <a href="http://www.w3.org/TR/SVGTiny12/interact.html#SVGEvents">SVG events</a> 929 * @param {string} type the event type. 930 * @param {function} handler the event handler. 931 * @returns {pv.Mark} this. 932 */ 933 pv.Mark.prototype.event = function(type, handler) { 934 if (!this.$handlers) this.$handlers = {}; 935 this.$handlers[type] = handler; 936 return this; 937 }; 938 939 /** @private TODO */ 940 pv.Mark.prototype.dispatch = function(type, scenes, index) { 941 var l = this.$handlers && this.$handlers[type]; 942 if (!l) { 943 if (this.parent) { 944 this.parent.dispatch(type, scenes.parent, scenes.parentIndex); 945 } 946 return; 947 } 948 try { 949 950 /* Setup the scene stack. */ 951 var mark = this; 952 do { 953 mark.index = index; 954 mark.scene = scenes; 955 index = scenes.parentIndex; 956 scenes = scenes.parent; 957 } while (mark = mark.parent); 958 959 /* Execute the event listener. */ 960 try { 961 mark = l.apply(this, this.root.scene.data = argv(this)); 962 } finally { 963 this.root.scene.data = null; 964 } 965 966 /* Update the display. TODO dirtying. */ 967 if (mark instanceof pv.Mark) mark.render(); 968 969 } finally { 970 971 /* Restore the scene stack. */ 972 var mark = this; 973 do { 974 if (mark.parent) delete mark.scene; 975 delete mark.index; 976 } while (mark = mark.parent); 977 } 978 }; 979