/** * Constructs a new mark with default properties. Marks, with the exception of * the root panel, are not typically constructed directly; instead, they are * added to a panel or an existing mark via {@link pv.Mark#add}. * * @class Represents a data-driven graphical mark. The Mark class is * the base class for all graphical marks in Protovis; it does not provide any * specific rendering functionality, but together with {@link Panel} establishes * the core framework. * *

Concrete mark types include familiar visual elements such as bars, lines * and labels. Although a bar mark may be used to construct a bar chart, marks * know nothing about charts; it is only through their specification and * composition that charts are produced. These building blocks permit many * combinatorial possibilities. * *

Marks are associated with data: a mark is generated once per * associated datum, mapping the datum to visual properties such as * position and color. Thus, a single mark specification represents a set of * visual elements that share the same data and visual encoding. The type of * mark defines the names of properties and their meaning. A property may be * static, ignoring the associated datum and returning a constant; or, it may be * dynamic, derived from the associated datum or index. Such dynamic encodings * can be specified succinctly using anonymous functions. Special properties * called event handlers can be registered to add interactivity. * *

Protovis uses inheritance to simplify the specification of related * marks: a new mark can be derived from an existing mark, inheriting its * properties. The new mark can then override properties to specify new * behavior, potentially in terms of the old behavior. In this way, the old mark * serves as the prototype for the new mark. Most mark types share the * same basic properties for consistency and to facilitate inheritance. * *

The prioritization of redundant properties is as follows:

    * *
  1. If the width property is not specified (i.e., null), its value * is the width of the parent panel, minus this mark's left and right margins; * the left and right margins are zero if not specified. * *
  2. Otherwise, if the right margin is not specified, its value is * the width of the parent panel, minus this mark's width and left margin; the * left margin is zero if not specified. * *
  3. Otherwise, if the left property is not specified, its value is * the width of the parent panel, minus this mark's width and the right margin. * *
This prioritization is then duplicated for the height, * bottom and top properties, respectively. * *

While most properties are variable, some mark types, such as lines * and areas, generate a single visual element rather than a distinct visual * element per datum. With these marks, some properties may be fixed. * Fixed properties can vary per mark, but not per datum! These * properties are evaluated solely for the first (0-index) datum, and typically * are specified as a constant. However, it is valid to use a function if the * property varies between panels or is dynamically generated. * *

See also the Protovis guide. */ pv.Mark = function() { /* * TYPE 0 constant defs * TYPE 1 function defs * TYPE 2 constant properties * TYPE 3 function properties * in order of evaluation! */ this.$properties = []; this.$handlers = {}; }; /** @private Records which properties are defined on this mark type. */ pv.Mark.prototype.properties = {}; /** @private Records the cast function for each property. */ pv.Mark.cast = {}; /** * @private Defines and registers a property method for the property with the * given name. This method should be called on a mark class prototype to define * each exposed property. (Note this refers to the JavaScript * prototype, not the Protovis mark prototype, which is the {@link * #proto} field.) * *

The created property method supports several modes of invocation:

    * *
  1. If invoked with a Function argument, this function is evaluated * for each associated datum. The return value of the function is used as the * computed property value. The context of the function (this) is this * mark. The arguments to the function are the associated data of this mark and * any enclosing panels. For example, a linear encoding of numerical data to * height is specified as * *
    m.height(function(d) d * 100);
    * * The expression d * 100 will be evaluated for the height property of * each mark instance. The return value of the property method (e.g., * m.height) is this mark (m)).

    * *

  2. If invoked with a non-function argument, the property is treated as a * constant. The return value of the property method (e.g., m.height) * is this mark.

    * *

  3. If invoked with no arguments, the computed property value for the current * mark instance in the scene graph is returned. This facilitates property * chaining, where one mark's properties are defined in terms of another's. * For example, to offset a mark's location from its prototype, you might say * *
    m.top(function() this.proto.top() + 10);
    * * Note that the index of the mark being evaluated (in the above example, * this.proto) is inherited from the Mark class and set by * this mark. So, if the fifth element's top property is being evaluated, the * fifth instance of this.proto will similarly be queried for the value * of its top property. If the mark being evaluated has a different number of * instances, or its data is unrelated, the behavior of this method is * undefined. In these cases it may be better to index the scene * explicitly to specify the exact instance. * *

Property names should follow standard JavaScript method naming * conventions, using lowerCamel-style capitalization. * *

In addition to creating the property method, every property is registered * in the {@link #properties} map on the prototype. Although this is an * instance field, it is considered immutable and shared by all instances of a * given mark type. The properties map can be queried to see if a mark * type defines a particular property, such as width or height. * * @param {string} name the property name. * @param {function} [cast] the cast function for this property. */ pv.Mark.prototype.property = function(name, cast) { if (!this.hasOwnProperty("properties")) { this.properties = pv.extend(this.properties); } this.properties[name] = true; /* * Define the setter-getter globally, since the default behavior should be the * same for all properties, and since the Protovis inheritance chain is * independent of the JavaScript inheritance chain. For example, anchors * define a "name" property that is evaluated on derived marks, even though * those marks don't normally have a name. */ pv.Mark.prototype.propertyMethod(name, false, pv.Mark.cast[name] = cast); return this; }; /** * @private Defines a setter-getter for the specified property. * *

If a cast function has been assigned to the specified property name, the * property function is wrapped by the cast function, or, if a constant is * specified, the constant is immediately cast. Note, however, that if the * property value is null, the cast function is not invoked. * * @param {string} name the property name. * @param {boolean} [def] whether is a property or a def. * @param {function} [cast] the cast function for this property. */ pv.Mark.prototype.propertyMethod = function(name, def, cast) { if (!cast) cast = pv.Mark.cast[name]; this[name] = function(v) { /* If this is a def, use it rather than property. */ if (def && this.scene) { var defs = this.scene.defs; if (arguments.length) { defs[name] = { id: (v == null) ? 0 : pv.id(), value: ((v != null) && cast) ? cast(v) : v }; return this; } return defs[name] ? defs[name].value : null; } /* If arguments are specified, set the property value. */ if (arguments.length) { var type = !def << 1 | (typeof v == "function"); this.propertyValue(name, (type & 1 && cast) ? function() { var x = v.apply(this, arguments); return (x != null) ? cast(x) : null; } : (((v != null) && cast) ? cast(v) : v)).type = type; return this; } return this.instance()[name]; }; }; /** @private Sets the value of the property name to v. */ pv.Mark.prototype.propertyValue = function(name, v) { var properties = this.$properties, p = {name: name, id: pv.id(), value: v}; for (var i = 0; i < properties.length; i++) { if (properties[i].name == name) { properties.splice(i, 1); break; } } properties.push(p); return p; }; /* Define all global properties. */ pv.Mark.prototype .property("data") .property("visible", Boolean) .property("left", Number) .property("right", Number) .property("top", Number) .property("bottom", Number) .property("cursor", String) .property("title", String) .property("reverse", Boolean) .property("antialias", Boolean) .property("events", String) .property("id", String); /** * The mark type; a lower camelCase name. The type name controls rendering * behavior, and unless the rendering engine is extended, must be one of the * built-in concrete mark types: area, bar, dot, image, label, line, panel, * rule, or wedge. * * @type string * @name pv.Mark.prototype.type */ /** * The mark prototype, possibly undefined, from which to inherit property * functions. The mark prototype is not necessarily of the same type as this * mark. Any properties defined on this mark will override properties inherited * either from the prototype or from the type-specific defaults. * * @type pv.Mark * @name pv.Mark.prototype.proto */ /** * The mark anchor target, possibly undefined. * * @type pv.Mark * @name pv.Mark.prototype.target */ /** * The enclosing parent panel. The parent panel is generally undefined only for * the root panel; however, it is possible to create "offscreen" marks that are * used only for inheritance purposes. * * @type pv.Panel * @name pv.Mark.prototype.parent */ /** * The child index. -1 if the enclosing parent panel is null; otherwise, the * zero-based index of this mark into the parent panel's children array. * * @type number */ pv.Mark.prototype.childIndex = -1; /** * The mark index. The value of this field depends on which instance (i.e., * which element of the data array) is currently being evaluated. During the * build phase, the index is incremented over each datum; when handling events, * the index is set to the instance that triggered the event. * * @type number */ pv.Mark.prototype.index = -1; /** * The current scale factor, based on any enclosing transforms. The current * scale can be used to create scale-independent graphics. For example, to * define a dot that has a radius of 10 irrespective of any zooming, say: * *

dot.shapeRadius(function() 10 / this.scale)
* * Note that the stroke width and font size are defined irrespective of scale * (i.e., in screen space) already. Also note that when a transform is applied * to a panel, the scale affects only the child marks, not the panel itself. * * @type number * @see pv.Panel#transform */ pv.Mark.prototype.scale = 1; /** * @private The scene graph. The scene graph is an array of objects; each object * (or "node") corresponds to an instance of this mark and an element in the * data array. The scene graph can be traversed to lookup previously-evaluated * properties. * * @name pv.Mark.prototype.scene */ /** * The root parent panel. This may be undefined for "offscreen" marks that are * created for inheritance purposes only. * * @type pv.Panel * @name pv.Mark.prototype.root */ /** * The data property; an array of objects. The size of the array determines the * number of marks that will be instantiated; each element in the array will be * passed to property functions to compute the property values. Typically, the * data property is specified as a constant array, such as * *
m.data([1, 2, 3, 4, 5]);
* * However, it is perfectly acceptable to define the data property as a * function. This function might compute the data dynamically, allowing * different data to be used per enclosing panel. For instance, in the stacked * area graph example (see {@link #scene}), the data function on the area mark * dereferences each series. * * @type array * @name pv.Mark.prototype.data */ /** * The visible property; a boolean determining whether or not the mark instance * is visible. If a mark instance is not visible, its other properties will not * be evaluated. Similarly, for panels no child marks will be rendered. * * @type boolean * @name pv.Mark.prototype.visible */ /** * The left margin; the distance, in pixels, between the left edge of the * enclosing panel and the left edge of this mark. Note that in some cases this * property may be redundant with the right property, or with the conjunction of * right and width. * * @type number * @name pv.Mark.prototype.left */ /** * The right margin; the distance, in pixels, between the right edge of the * enclosing panel and the right edge of this mark. Note that in some cases this * property may be redundant with the left property, or with the conjunction of * left and width. * * @type number * @name pv.Mark.prototype.right */ /** * The top margin; the distance, in pixels, between the top edge of the * enclosing panel and the top edge of this mark. Note that in some cases this * property may be redundant with the bottom property, or with the conjunction * of bottom and height. * * @type number * @name pv.Mark.prototype.top */ /** * The bottom margin; the distance, in pixels, between the bottom edge of the * enclosing panel and the bottom edge of this mark. Note that in some cases * this property may be redundant with the top property, or with the conjunction * of top and height. * * @type number * @name pv.Mark.prototype.bottom */ /** * The cursor property; corresponds to the CSS cursor property. This is * typically used in conjunction with event handlers to indicate interactivity. * * @type string * @name pv.Mark.prototype.cursor * @see CSS2 cursor */ /** * The title property; corresponds to the HTML/SVG title property, allowing the * general of simple plain text tooltips. * * @type string * @name pv.Mark.prototype.title */ /** * The events property; corresponds to the SVG pointer-events property, * specifying how the mark should participate in mouse events. The default value * is "painted". Supported values are: * *

"painted": The given mark may receive events when the mouse is over a * "painted" area. The painted areas are the interior (i.e., fill) of the mark * if a 'fillStyle' is specified, and the perimeter (i.e., stroke) of the mark * if a 'strokeStyle' is specified. * *

"all": The given mark may receive events when the mouse is over either the * interior (i.e., fill) or the perimeter (i.e., stroke) of the mark, regardless * of the specified fillStyle and strokeStyle. * *

"none": The given mark may not receive events. * * @type string * @name pv.Mark.prototype.events */ /** * The reverse property; a boolean determining whether marks are ordered from * front-to-back or back-to-front. SVG does not support explicit z-ordering; * shapes are rendered in the order they appear. Thus, by default, marks are * rendered in data order. Setting the reverse property to false reverses the * order in which they are rendered; however, the properties are still evaluated * (i.e., built) in forward order. * * @type boolean * @name pv.Mark.prototype.reverse */ /** * The instance identifier, for correspondence across animated transitions. If * no identifier is specified, correspondence is determined using the mark * index. Identifiers are not global, but local to a given mark. * * @type String * @name pv.Mark.prototype.id */ /** * Default properties for all mark types. By default, the data array is the * parent data as a single-element array; if the data property is not specified, * this causes each mark to be instantiated as a singleton with the parents * datum. The visible property is true by default, and the reverse property is * false. * * @type pv.Mark */ pv.Mark.prototype.defaults = new pv.Mark() .data(function(d) { return [d]; }) .visible(true) .antialias(true) .events("painted"); /** * Sets the prototype of this mark to the specified mark. Any properties not * defined on this mark may be inherited from the specified prototype mark, or * its prototype, and so on. The prototype mark need not be the same type of * mark as this mark. (Note that for inheritance to be useful, properties with * the same name on different mark types should have equivalent meaning.) * * @param {pv.Mark} proto the new prototype. * @returns {pv.Mark} this mark. * @see #add */ pv.Mark.prototype.extend = function(proto) { this.proto = proto; this.target = proto.target; return this; }; /** * Adds a new mark of the specified type to the enclosing parent panel, whilst * simultaneously setting the prototype of the new mark to be this mark. * * @param {function} type the type of mark to add; a constructor, such as * pv.Bar. * @returns {pv.Mark} the new mark. * @see #extend */ pv.Mark.prototype.add = function(type) { return this.parent.add(type).extend(this); }; /** * Defines a custom property on this mark. Custom properties are currently * fixed, in that they are initialized once per mark set (i.e., per parent panel * instance). Custom properties can be used to store local state for the mark, * such as data needed by other properties (e.g., a custom scale) or interaction * state. * *

WARNING We plan on changing this feature in a future release to define * standard properties, as opposed to fixed properties that behave * idiosyncratically within event handlers. Furthermore, we recommend storing * state in an external data structure, rather than tying it to the * visualization specification as with defs. * * @param {string} name the name of the local variable. * @param {function} [v] an optional initializer; may be a constant or a * function. */ pv.Mark.prototype.def = function(name, v) { this.propertyMethod(name, true); return this[name](arguments.length > 1 ? v : null); }; /** * Returns an anchor with the specified name. All marks support the five * standard anchor names:

In addition to positioning properties (left, right, top bottom), the * anchors support text rendering properties (text-align, text-baseline). Text is * rendered to appear inside the mark by default. * *

To facilitate stacking, anchors are defined in terms of their opposite * edge. For example, the top anchor defines the bottom property, such that the * mark extends upwards; the bottom anchor instead defines the top property, * such that the mark extends downwards. See also {@link pv.Layout.Stack}. * *

While anchor names are typically constants, the anchor name is a true * property, which means you can specify a function to compute the anchor name * dynamically. See the {@link pv.Anchor#name} property for details. * * @param {string} name the anchor name; either a string or a property function. * @returns {pv.Anchor} the new anchor. */ pv.Mark.prototype.anchor = function(name) { if (!name) name = "center"; // default anchor name return new pv.Anchor(this) .name(name) .data(function() { return this.scene.target.map(function(s) { return s.data; }); }) .visible(function() { return this.scene.target[this.index].visible; }) .id(function() { return this.scene.target[this.index].id; }) .left(function() { var s = this.scene.target[this.index], w = s.width || 0; switch (this.name()) { case "bottom": case "top": case "center": return s.left + w / 2; case "left": return null; } return s.left + w; }) .top(function() { var s = this.scene.target[this.index], h = s.height || 0; switch (this.name()) { case "left": case "right": case "center": return s.top + h / 2; case "top": return null; } return s.top + h; }) .right(function() { var s = this.scene.target[this.index]; return this.name() == "left" ? s.right + (s.width || 0) : null; }) .bottom(function() { var s = this.scene.target[this.index]; return this.name() == "top" ? s.bottom + (s.height || 0) : null; }) .textAlign(function() { switch (this.name()) { case "bottom": case "top": case "center": return "center"; case "right": return "right"; } return "left"; }) .textBaseline(function() { switch (this.name()) { case "right": case "left": case "center": return "middle"; case "top": return "top"; } return "bottom"; }); }; /** @deprecated Replaced by {@link #target}. */ pv.Mark.prototype.anchorTarget = function() { return this.target; }; /** * Alias for setting the left, right, top and bottom properties simultaneously. * * @see #left * @see #right * @see #top * @see #bottom * @returns {pv.Mark} this. */ pv.Mark.prototype.margin = function(n) { return this.left(n).right(n).top(n).bottom(n); }; /** * @private Returns the current instance of this mark in the scene graph. This * is typically equivalent to this.scene[this.index], however if the * scene or index is unset, the default instance of the mark is returned. If no * default is set, the default is the last instance. Similarly, if the scene or * index of the parent panel is unset, the default instance of this mark in the * last instance of the enclosing panel is returned, and so on. * * @returns a node in the scene graph. */ pv.Mark.prototype.instance = function(defaultIndex) { var scene = this.scene || this.parent.instance(-1).children[this.childIndex], index = !arguments.length || this.hasOwnProperty("index") ? this.index : defaultIndex; return scene[index < 0 ? scene.length - 1 : index]; }; /** * @private Find the instances of this mark that match source. * * @see pv.Anchor */ pv.Mark.prototype.instances = function(source) { var mark = this, index = [], scene; /* Mirrored descent. */ while (!(scene = mark.scene)) { source = source.parent; index.push({index: source.index, childIndex: mark.childIndex}); mark = mark.parent; } while (index.length) { var i = index.pop(); scene = scene[i.index].children[i.childIndex]; } /* * When the anchor target is also an ancestor, as in the case of adding * to a panel anchor, only generate one instance per panel. Also, set * the margins to zero, since they are offset by the enclosing panel. */ if (this.hasOwnProperty("index")) { var s = pv.extend(scene[this.index]); s.right = s.top = s.left = s.bottom = 0; return [s]; } return scene; }; /** * @private Returns the first instance of this mark in the scene graph. This * method can only be called when the mark is bound to the scene graph (for * example, from an event handler, or within a property function). * * @returns a node in the scene graph. */ pv.Mark.prototype.first = function() { return this.scene[0]; }; /** * @private Returns the last instance of this mark in the scene graph. This * method can only be called when the mark is bound to the scene graph (for * example, from an event handler, or within a property function). In addition, * note that mark instances are built sequentially, so the last instance of this * mark may not yet be constructed. * * @returns a node in the scene graph. */ pv.Mark.prototype.last = function() { return this.scene[this.scene.length - 1]; }; /** * @private Returns the previous instance of this mark in the scene graph, or * null if this is the first instance. * * @returns a node in the scene graph, or null. */ pv.Mark.prototype.sibling = function() { return (this.index == 0) ? null : this.scene[this.index - 1]; }; /** * @private Returns the current instance in the scene graph of this mark, in the * previous instance of the enclosing parent panel. May return null if this * instance could not be found. * * @returns a node in the scene graph, or null. */ pv.Mark.prototype.cousin = function() { var p = this.parent, s = p && p.sibling(); return (s && s.children) ? s.children[this.childIndex][this.index] : null; }; /** * Renders this mark, including recursively rendering all child marks if this is * a panel. This method finds all instances of this mark and renders them. This * method descends recursively to the level of the mark to be rendered, finding * all visible instances of the mark. After the marks are rendered, the scene * and index attributes are removed from the mark to restore them to a clean * state. * *

If an enclosing panel has an index property set (as is the case inside in * an event handler), then only instances of this mark inside the given instance * of the panel will be rendered; otherwise, all visible instances of the mark * will be rendered. */ pv.Mark.prototype.render = function() { var parent = this.parent, stack = pv.Mark.stack; /* For the first render, take it from the top. */ if (parent && !this.root.scene) { this.root.render(); return; } /* Record the path to this mark. */ var indexes = []; for (var mark = this; mark.parent; mark = mark.parent) { indexes.unshift(mark.childIndex); } /** @private */ function render(mark, depth, scale) { mark.scale = scale; if (depth < indexes.length) { stack.unshift(null); if (mark.hasOwnProperty("index")) { renderInstance(mark, depth, scale); } else { for (var i = 0, n = mark.scene.length; i < n; i++) { mark.index = i; renderInstance(mark, depth, scale); } delete mark.index; } stack.shift(); } else { mark.build(); /* * In the update phase, the scene is rendered by creating and updating * elements and attributes in the SVG image. No properties are evaluated * during the update phase; instead the values computed previously in the * build phase are simply translated into SVG. The update phase is * decoupled (see pv.Scene) to allow different rendering engines. */ pv.Scene.scale = scale; pv.Scene.updateAll(mark.scene); } delete mark.scale; } /** * @private Recursively renders the current instance of the specified mark. * This is slightly tricky because `index` and `scene` properties may or may * not already be set; if they are set, it means we are rendering only a * specific instance; if they are unset, we are rendering all instances. * Furthermore, we must preserve the original context of these properties when * rendering completes. * *

Another tricky aspect is that the `scene` attribute should be set for * any preceding children, so as to allow property chaining. This is * consistent with first-pass rendering. */ function renderInstance(mark, depth, scale) { var s = mark.scene[mark.index], i; if (s.visible) { var childIndex = indexes[depth], child = mark.children[childIndex]; /* Set preceding child scenes. */ for (i = 0; i < childIndex; i++) { mark.children[i].scene = s.children[i]; } /* Set current child scene, if necessary. */ stack[0] = s.data; if (child.scene) { render(child, depth + 1, scale * s.transform.k); } else { child.scene = s.children[childIndex]; render(child, depth + 1, scale * s.transform.k); delete child.scene; } /* Clear preceding child scenes. */ for (i = 0; i < childIndex; i++) { delete mark.children[i].scene; } } } /* Bind this mark's property definitions. */ this.bind(); /* The render context is the first ancestor with an explicit index. */ while (parent && !parent.hasOwnProperty("index")) parent = parent.parent; /* Recursively render all instances of this mark. */ this.context( parent ? parent.scene : undefined, parent ? parent.index : -1, function() { render(this.root, 0, 1); }); }; /** @private Stores the current data stack. */ pv.Mark.stack = []; /** * @private In the bind phase, inherited property definitions are cached so they * do not need to be queried during build. */ pv.Mark.prototype.bind = function() { var seen = {}, types = [[], [], [], []], data, required = []; /** Scans the proto chain for the specified mark. */ function bind(mark) { do { var properties = mark.$properties; for (var i = properties.length - 1; i >= 0 ; i--) { var p = properties[i]; if (!(p.name in seen)) { seen[p.name] = p; switch (p.name) { case "data": data = p; break; case "visible": case "id": required.push(p); break; default: types[p.type].push(p); break; } } } } while (mark = mark.proto); } /* Scan the proto chain for all defined properties. */ bind(this); bind(this.defaults); types[1].reverse(); types[3].reverse(); /* Any undefined properties are null. */ var mark = this; do for (var name in mark.properties) { if (!(name in seen)) { types[2].push(seen[name] = {name: name, type: 2, value: null}); } } while (mark = mark.proto); /* Define setter-getter for inherited defs. */ var defs = types[0].concat(types[1]); for (var i = 0; i < defs.length; i++) { this.propertyMethod(defs[i].name, true); } /* Setup binds to evaluate constants before functions. */ this.binds = { properties: seen, data: data, defs: defs, required: required, optional: pv.blend(types) }; }; /** * @private Evaluates properties and computes implied properties. Properties are * stored in the {@link #scene} array for each instance of this mark. * *

As marks are built recursively, the {@link #index} property is updated to * match the current index into the data array for each mark. Note that the * index property is only set for the mark currently being built and its * enclosing parent panels. The index property for other marks is unset, but is * inherited from the global Mark class prototype. This allows mark * properties to refer to properties on other marks in the same panel * conveniently; however, in general it is better to reference mark instances * specifically through the scene graph rather than depending on the magical * behavior of {@link #index}. * *

The root scene array has a special property, data, which stores * the current data stack. The first element in this stack is the current datum, * followed by the datum of the enclosing parent panel, and so on. The data * stack should not be accessed directly; instead, property functions are passed * the current data stack as arguments. * *

The evaluation of the data and visible properties is * special. The data property is evaluated first; unlike the other * properties, the data stack is from the parent panel, rather than the current * mark, since the data is not defined until the data property is evaluated. * The visible property is subsequently evaluated for each instance; * only if true will the {@link #buildInstance} method be called, evaluating * other properties and recursively building the scene graph. * *

If this mark is being re-built, any old instances of this mark that no * longer exist (because the new data array contains fewer elements) will be * cleared using {@link #clearInstance}. * * @param parent the instance of the parent panel from the scene graph. */ pv.Mark.prototype.build = function() { var scene = this.scene, stack = pv.Mark.stack; if (!scene) { scene = this.scene = []; scene.mark = this; scene.type = this.type; scene.childIndex = this.childIndex; if (this.parent) { scene.parent = this.parent.scene; scene.parentIndex = this.parent.index; } } /* Resolve anchor target. */ if (this.target) scene.target = this.target.instances(scene); /* Evaluate defs. */ if (this.binds.defs.length) { var defs = scene.defs; if (!defs) scene.defs = defs = {}; for (var i = 0; i < this.binds.defs.length; i++) { var p = this.binds.defs[i], d = defs[p.name]; if (!d || (p.id > d.id)) { defs[p.name] = { id: 0, // this def will be re-evaluated on next build value: (p.type & 1) ? p.value.apply(this, stack) : p.value }; } } } /* Evaluate special data property. */ var data = this.binds.data; data = data.type & 1 ? data.value.apply(this, stack) : data.value; /* Create, update and delete scene nodes. */ stack.unshift(null); scene.length = data.length; for (var i = 0; i < data.length; i++) { pv.Mark.prototype.index = this.index = i; var s = scene[i]; if (!s) scene[i] = s = {}; s.data = stack[0] = data[i]; this.buildInstance(s); } pv.Mark.prototype.index = -1; delete this.index; stack.shift(); return this; }; /** * @private Evaluates the specified array of properties for the specified * instance s in the scene graph. * * @param s a node in the scene graph; the instance of the mark to build. * @param properties an array of properties. */ pv.Mark.prototype.buildProperties = function(s, properties) { for (var i = 0, n = properties.length; i < n; i++) { var p = properties[i], v = p.value; // assume case 2 (constant) switch (p.type) { case 0: case 1: v = this.scene.defs[p.name].value; break; case 3: v = v.apply(this, pv.Mark.stack); break; } s[p.name] = v; } }; /** * @private Evaluates all of the properties for this mark for the specified * instance s in the scene graph. The set of properties to evaluate is * retrieved from the {@link #properties} array for this mark type (see {@link * #type}). After these properties are evaluated, any implied properties * may be computed by the mark and set on the scene graph; see * {@link #buildImplied}. * *

For panels, this method recursively builds the scene graph for all child * marks as well. In general, this method should not need to be overridden by * concrete mark types. * * @param s a node in the scene graph; the instance of the mark to build. */ pv.Mark.prototype.buildInstance = function(s) { this.buildProperties(s, this.binds.required); if (s.visible) { this.buildProperties(s, this.binds.optional); this.buildImplied(s); } }; /** * @private Computes the implied properties for this mark for the specified * instance s in the scene graph. Implied properties are those with * dependencies on multiple other properties; for example, the width property * may be implied if the left and right properties are set. This method can be * overridden by concrete mark types to define new implied properties, if * necessary. * * @param s a node in the scene graph; the instance of the mark to build. */ pv.Mark.prototype.buildImplied = function(s) { var l = s.left; var r = s.right; var t = s.top; var b = s.bottom; /* Assume width and height are zero if not supported by this mark type. */ var p = this.properties; var w = p.width ? s.width : 0; var h = p.height ? s.height : 0; /* Compute implied width, right and left. */ var width = this.parent ? this.parent.width() : (w + l + r); if (w == null) { w = width - (r = r || 0) - (l = l || 0); } else if (r == null) { if (l == null) { l = r = (width - w) / 2; } else { r = width - w - l; } } else if (l == null) { l = width - w - r; } /* Compute implied height, bottom and top. */ var height = this.parent ? this.parent.height() : (h + t + b); if (h == null) { h = height - (t = t || 0) - (b = b || 0); } else if (b == null) { if (t == null) { b = t = (height - h) / 2; } else { b = height - h - t; } } else if (t == null) { t = height - h - b; } s.left = l; s.right = r; s.top = t; s.bottom = b; /* Only set width and height if they are supported by this mark type. */ if (p.width) s.width = w; if (p.height) s.height = h; /* Set any null colors to pv.Color.transparent. */ if (p.textStyle && !s.textStyle) s.textStyle = pv.Color.transparent; if (p.fillStyle && !s.fillStyle) s.fillStyle = pv.Color.transparent; if (p.strokeStyle && !s.strokeStyle) s.strokeStyle = pv.Color.transparent; }; /** * Returns the current location of the mouse (cursor) relative to this mark's * parent. The x coordinate corresponds to the left margin, while the * y coordinate corresponds to the top margin. * * @returns {pv.Vector} the mouse location. */ pv.Mark.prototype.mouse = function() { /* Compute xy-coordinates relative to the panel. */ var x = pv.event.pageX || 0, y = pv.event.pageY || 0, n = this.root.canvas(); do { x -= n.offsetLeft; y -= n.offsetTop; } while (n = n.offsetParent); /* Compute the inverse transform of all enclosing panels. */ var t = pv.Transform.identity, p = this.properties.transform ? this : this.parent, pz = []; do { pz.push(p); } while (p = p.parent); while (p = pz.pop()) t = t.translate(p.left(), p.top()).times(p.transform()); t = t.invert(); return pv.vector(x * t.k + t.x, y * t.k + t.y); }; /** * Registers an event handler for the specified event type with this mark. When * an event of the specified type is triggered, the specified handler will be * invoked. The handler is invoked in a similar method to property functions: * the context is this mark instance, and the arguments are the full * data stack. Event handlers can use property methods to manipulate the display * properties of the mark: * *

m.event("click", function() this.fillStyle("red"));
* * Alternatively, the external data can be manipulated and the visualization * redrawn: * *
m.event("click", function(d) {
 *     data = all.filter(function(k) k.name == d);
 *     vis.render();
 *   });
* * The return value of the event handler determines which mark gets re-rendered. * Use defs ({@link #def}) to set temporary state from event handlers. * *

The complete set of event types is defined by SVG; see the reference * below. The set of supported event types is:

Since Protovis does not specify any concept of focus, it does not * support key events; these should be handled outside the visualization using * standard JavaScript. In the future, support for interaction may be extended * to support additional event types, particularly those most relevant to * interactive visualization, such as selection. * *

TODO In the current implementation, event handlers are not inherited from * prototype marks. They must be defined explicitly on each interactive mark. In * addition, only one event handler for a given event type can be defined; when * specifying multiple event handlers for the same type, only the last one will * be used. * * @see SVG events * @param {string} type the event type. * @param {function} handler the event handler. * @returns {pv.Mark} this. */ pv.Mark.prototype.event = function(type, handler) { this.$handlers[type] = pv.functor(handler); return this; }; /** @private Evaluates the function f with the specified context. */ pv.Mark.prototype.context = function(scene, index, f) { var proto = pv.Mark.prototype, stack = pv.Mark.stack, oscene = pv.Mark.scene, oindex = proto.index; /** @private Sets the context. */ function apply(scene, index) { pv.Mark.scene = scene; proto.index = index; if (!scene) return; var that = scene.mark, mark = that, ancestors = []; /* Set ancestors' scene and index; populate data stack. */ do { ancestors.push(mark); stack.push(scene[index].data); mark.index = index; mark.scene = scene; index = scene.parentIndex; scene = scene.parent; } while (mark = mark.parent); /* Set ancestors' scale; requires top-down. */ for (var i = ancestors.length - 1, k = 1; i > 0; i--) { mark = ancestors[i]; mark.scale = k; k *= mark.scene[mark.index].transform.k; } /* Set children's scene and scale. */ if (that.children) for (var i = 0, n = that.children.length; i < n; i++) { mark = that.children[i]; mark.scene = that.scene[that.index].children[i]; mark.scale = k; } } /** @private Clears the context. */ function clear(scene, index) { if (!scene) return; var that = scene.mark, mark; /* Reset children. */ if (that.children) for (var i = 0, n = that.children.length; i < n; i++) { mark = that.children[i]; delete mark.scene; delete mark.scale; } /* Reset ancestors. */ mark = that; do { stack.pop(); if (mark.parent) { delete mark.scene; delete mark.scale; } delete mark.index; } while (mark = mark.parent); } /* Context switch, invoke the function, then switch back. */ clear(oscene, oindex); apply(scene, index); try { f.apply(this, stack); } finally { clear(scene, index); apply(oscene, oindex); } }; /** @private Execute the event listener, then re-render. */ pv.Mark.dispatch = function(type, scene, index) { var m = scene.mark, p = scene.parent, l = m.$handlers[type]; if (!l) return p && pv.Mark.dispatch(type, p, scene.parentIndex); m.context(scene, index, function() { m = l.apply(m, pv.Mark.stack); if (m && m.render) m.render(); }); return true; }; pv.Mark.prototype.transition = function() { return new pv.Transition(this); }; pv.Mark.prototype.on = function(state) { return this["$" + state] = new pv.Transient(this); };