/** * Constructs a new, empty panel with default properties. Panels, with the * exception of the root panel, are not typically constructed directly; instead, * they are added to an existing panel or mark via {@link pv.Mark#add}. * * @class Represents a container mark. Panels allow repeated or nested * structures, commonly used in small multiple displays where a small * visualization is tiled to facilitate comparison across one or more * dimensions. Other types of visualizations may benefit from repeated and * possibly overlapping structure as well, such as stacked area charts. Panels * can also offset the position of marks to provide padding from surrounding * content. * *

All Protovis displays have at least one panel; this is the root panel to * which marks are rendered. The box model properties (four margins, width and * height) are used to offset the positions of contained marks. The data * property determines the panel count: a panel is generated once per associated * datum. When nested panels are used, property functions can declare additional * arguments to access the data associated with enclosing panels. * *

Panels can be rendered inline, facilitating the creation of sparklines. * This allows designers to reuse browser layout features, such as text flow and * tables; designers can also overlay HTML elements such as rich text and * images. * *

All panels have a children array (possibly empty) containing the * child marks in the order they were added. Panels also have a root * field which points to the root (outermost) panel; the root panel's root field * points to itself. * *

See also the Protovis guide. * * @extends pv.Bar */ pv.Panel = function() { pv.Bar.call(this); /** * The child marks; zero or more {@link pv.Mark}s in the order they were * added. * * @see #add * @type pv.Mark[] */ this.children = []; this.root = this; /** * The internal $dom field is set by the Protovis loader; see lang/init.js. It * refers to the script element that contains the Protovis specification, so * that the panel knows where in the DOM to insert the generated SVG element. * * @private */ this.$dom = pv.$ && pv.$.s; }; pv.Panel.prototype = pv.extend(pv.Bar) .property("transform") .property("overflow", String) .property("canvas", function(c) { return (typeof c == "string") ? document.getElementById(c) : c; // assume that c is the passed-in element }); pv.Panel.prototype.type = "panel"; /** * The canvas element; either the string ID of the canvas element in the current * document, or a reference to the canvas element itself. If null, a canvas * element will be created and inserted into the document at the location of the * script element containing the current Protovis specification. This property * only applies to root panels and is ignored on nested panels. * *

Note: the "canvas" element here refers to a div (or other suitable * HTML container element), not a canvas element. The name of * this property is a historical anachronism from the first implementation that * used HTML 5 canvas, rather than SVG. * * @type string * @name pv.Panel.prototype.canvas */ /** * Specifies whether child marks are clipped when they overflow this panel. * This affects the clipping of all this panel's descendant marks. * * @type string * @name pv.Panel.prototype.overflow * @see CSS2 */ /** * The transform to be applied to child marks. The default transform is * identity, which has no effect. Note that the panel's own fill and stroke are * not affected by the transform, and panel's transform only affects the * scale of child marks, not the panel itself. * * @type pv.Transform * @name pv.Panel.prototype.transform * @see pv.Mark#scale */ /** * Default properties for panels. By default, the margins are zero, the fill * style is transparent. * * @type pv.Panel */ pv.Panel.prototype.defaults = new pv.Panel() .extend(pv.Bar.prototype.defaults) .fillStyle(null) // override Bar default .overflow("visible"); /** * Returns an anchor with the specified name. This method is overridden such * that adding to a panel's anchor adds to the panel, rather than to the panel's * parent. * * @param {string} name the anchor name; either a string or a property function. * @returns {pv.Anchor} the new anchor. */ pv.Panel.prototype.anchor = function(name) { var anchor = pv.Bar.prototype.anchor.call(this, name); anchor.parent = this; return anchor; }; /** * Adds a new mark of the specified type to this panel. Unlike the normal * {@link Mark#add} behavior, adding a mark to a panel does not cause the mark * to inherit from the panel. Since the contained marks are offset by the panel * margins already, inheriting properties is generally undesirable; of course, * it is always possible to change this behavior by calling {@link Mark#extend} * explicitly. * * @param {function} type the type of the new mark to add. * @returns {pv.Mark} the new mark. */ pv.Panel.prototype.add = function(type) { var child = new type(); child.parent = this; child.root = this.root; child.childIndex = this.children.length; this.children.push(child); return child; }; /** @private Bind this panel, then any child marks recursively. */ pv.Panel.prototype.bind = function() { pv.Mark.prototype.bind.call(this); for (var i = 0; i < this.children.length; i++) { this.children[i].bind(); } }; /** * @private Evaluates all of the properties for this panel for the specified * instance s in the scene graph, including recursively building the * scene graph for child marks. * * @param s a node in the scene graph; the instance of the panel to build. * @see Mark#scene */ pv.Panel.prototype.buildInstance = function(s) { pv.Bar.prototype.buildInstance.call(this, s); if (!s.visible) return; if (!s.children) s.children = []; /* * Multiply the current scale factor by this panel's transform. Also clear the * default index as we recurse into child marks; it will be reset to the * current index when the next panel instance is built. */ var scale = this.scale * s.transform.k, child, n = this.children.length; pv.Mark.prototype.index = -1; /* * Build each child, passing in the parent (this panel) scene graph node. The * child mark's scene is initialized from the corresponding entry in the * existing scene graph, such that properties from the previous build can be * reused; this is largely to facilitate the recycling of SVG elements. */ for (var i = 0; i < n; i++) { child = this.children[i]; child.scene = s.children[i]; // possibly undefined child.scale = scale; child.build(); } /* * Once the child marks have been built, the new scene graph nodes are removed * from the child marks and placed into the scene graph. The nodes cannot * remain on the child nodes because this panel (or a parent panel) may be * instantiated multiple times! */ for (var i = 0; i < n; i++) { child = this.children[i]; s.children[i] = child.scene; delete child.scene; delete child.scale; } /* Delete any expired child scenes. */ s.children.length = n; }; /** * @private Computes the implied properties for this panel for the specified * instance s in the scene graph. Panels have two implied * properties:

The current implementation creates the SVG element, if necessary, during * the build phase; in the future, it may be preferrable to move this to the * update phase, although then the canvas property would be undefined. In * addition, DOM inspection is necessary to define the implied width and height * properties that may be inferred from the DOM. * * @param s a node in the scene graph; the instance of the panel to build. */ pv.Panel.prototype.buildImplied = function(s) { if (!this.parent) { var c = s.canvas; if (c) { /* Clear the container if it's not associated with this panel. */ if (c.$panel != this) { c.$panel = this; while (c.lastChild) c.removeChild(c.lastChild); } /* If width and height weren't specified, inspect the container. */ var w, h; if (s.width == null) { w = parseFloat(pv.css(c, "width")); s.width = w - s.left - s.right; } if (s.height == null) { h = parseFloat(pv.css(c, "height")); s.height = h - s.top - s.bottom; } } else { var cache = this.$canvas || (this.$canvas = []); if (!(c = cache[this.index])) { c = cache[this.index] = document.createElement("span"); if (this.$dom) { // script element for text/javascript+protovis this.$dom.parentNode.insertBefore(c, this.$dom); } else { // find the last element in the body var n = document.body; while (n.lastChild && n.lastChild.tagName) n = n.lastChild; if (n != document.body) n = n.parentNode; n.appendChild(c); } } } s.canvas = c; } if (!s.transform) s.transform = pv.Transform.identity; pv.Mark.prototype.buildImplied.call(this, s); };