1 /** 2 * Constructs a new, empty panel with default properties. Panels, with the 3 * exception of the root panel, are not typically constructed directly; instead, 4 * they are added to an existing panel or mark via {@link pv.Mark#add}. 5 * 6 * @class Represents a container mark. Panels allow repeated or nested 7 * structures, commonly used in small multiple displays where a small 8 * visualization is tiled to facilitate comparison across one or more 9 * dimensions. Other types of visualizations may benefit from repeated and 10 * possibly overlapping structure as well, such as stacked area charts. Panels 11 * can also offset the position of marks to provide padding from surrounding 12 * content. 13 * 14 * <p>All Protovis displays have at least one panel; this is the root panel to 15 * which marks are rendered. The box model properties (four margins, width and 16 * height) are used to offset the positions of contained marks. The data 17 * property determines the panel count: a panel is generated once per associated 18 * datum. When nested panels are used, property functions can declare additional 19 * arguments to access the data associated with enclosing panels. 20 * 21 * <p>Panels can be rendered inline, facilitating the creation of sparklines. 22 * This allows designers to reuse browser layout features, such as text flow and 23 * tables; designers can also overlay HTML elements such as rich text and 24 * images. 25 * 26 * <p>All panels have a <tt>children</tt> array (possibly empty) containing the 27 * child marks in the order they were added. Panels also have a <tt>root</tt> 28 * field which points to the root (outermost) panel; the root panel's root field 29 * points to itself. 30 * 31 * <p>See also the <a href="../../api/">Protovis guide</a>. 32 * 33 * @extends pv.Bar 34 */ 35 pv.Panel = function() { 36 pv.Bar.call(this); 37 38 /** 39 * The child marks; zero or more {@link pv.Mark}s in the order they were 40 * added. 41 * 42 * @see #add 43 * @type pv.Mark[] 44 */ 45 this.children = []; 46 this.root = this; 47 48 /** 49 * The internal $dom field is set by the Protovis loader; see lang/init.js. It 50 * refers to the script element that contains the Protovis specification, so 51 * that the panel knows where in the DOM to insert the generated SVG element. 52 * 53 * @private 54 */ 55 this.$dom = pv.$ && pv.$.s; 56 }; 57 58 pv.Panel.prototype = pv.extend(pv.Bar) 59 .property("transform") 60 .property("overflow", String) 61 .property("canvas", function(c) { 62 return (typeof c == "string") 63 ? document.getElementById(c) 64 : c; // assume that c is the passed-in element 65 }); 66 67 pv.Panel.prototype.type = "panel"; 68 69 /** 70 * The canvas element; either the string ID of the canvas element in the current 71 * document, or a reference to the canvas element itself. If null, a canvas 72 * element will be created and inserted into the document at the location of the 73 * script element containing the current Protovis specification. This property 74 * only applies to root panels and is ignored on nested panels. 75 * 76 * <p>Note: the "canvas" element here refers to a <tt>div</tt> (or other suitable 77 * HTML container element), <i>not</i> a <tt>canvas</tt> element. The name of 78 * this property is a historical anachronism from the first implementation that 79 * used HTML 5 canvas, rather than SVG. 80 * 81 * @type string 82 * @name pv.Panel.prototype.canvas 83 */ 84 85 /** 86 * Specifies whether child marks are clipped when they overflow this panel. 87 * This affects the clipping of all this panel's descendant marks. 88 * 89 * @type string 90 * @name pv.Panel.prototype.overflow 91 * @see <a href="http://www.w3.org/TR/CSS2/visufx.html#overflow">CSS2</a> 92 */ 93 94 /** 95 * The transform to be applied to child marks. The default transform is 96 * identity, which has no effect. Note that the panel's own fill and stroke are 97 * not affected by the transform, and panel's transform only affects the 98 * <tt>scale</tt> of child marks, not the panel itself. 99 * 100 * @type pv.Transform 101 * @name pv.Panel.prototype.transform 102 * @see pv.Mark#scale 103 */ 104 105 /** 106 * Default properties for panels. By default, the margins are zero, the fill 107 * style is transparent. 108 * 109 * @type pv.Panel 110 */ 111 pv.Panel.prototype.defaults = new pv.Panel() 112 .extend(pv.Bar.prototype.defaults) 113 .fillStyle(null) // override Bar default 114 .overflow("visible"); 115 116 /** 117 * Returns an anchor with the specified name. This method is overridden such 118 * that adding to a panel's anchor adds to the panel, rather than to the panel's 119 * parent. 120 * 121 * @param {string} name the anchor name; either a string or a property function. 122 * @returns {pv.Anchor} the new anchor. 123 */ 124 pv.Panel.prototype.anchor = function(name) { 125 var anchor = pv.Bar.prototype.anchor.call(this, name); 126 anchor.parent = this; 127 return anchor; 128 }; 129 130 /** 131 * Adds a new mark of the specified type to this panel. Unlike the normal 132 * {@link Mark#add} behavior, adding a mark to a panel does not cause the mark 133 * to inherit from the panel. Since the contained marks are offset by the panel 134 * margins already, inheriting properties is generally undesirable; of course, 135 * it is always possible to change this behavior by calling {@link Mark#extend} 136 * explicitly. 137 * 138 * @param {function} type the type of the new mark to add. 139 * @returns {pv.Mark} the new mark. 140 */ 141 pv.Panel.prototype.add = function(type) { 142 var child = new type(); 143 child.parent = this; 144 child.root = this.root; 145 child.childIndex = this.children.length; 146 this.children.push(child); 147 return child; 148 }; 149 150 /** @private Bind this panel, then any child marks recursively. */ 151 pv.Panel.prototype.bind = function() { 152 pv.Mark.prototype.bind.call(this); 153 for (var i = 0; i < this.children.length; i++) { 154 this.children[i].bind(); 155 } 156 }; 157 158 /** 159 * @private Evaluates all of the properties for this panel for the specified 160 * instance <tt>s</tt> in the scene graph, including recursively building the 161 * scene graph for child marks. 162 * 163 * @param s a node in the scene graph; the instance of the panel to build. 164 * @see Mark#scene 165 */ 166 pv.Panel.prototype.buildInstance = function(s) { 167 pv.Bar.prototype.buildInstance.call(this, s); 168 if (!s.visible) return; 169 if (!s.children) s.children = []; 170 171 /* 172 * Multiply the current scale factor by this panel's transform. Also clear the 173 * default index as we recurse into child marks; it will be reset to the 174 * current index when the next panel instance is built. 175 */ 176 var scale = this.scale * s.transform.k, child, n = this.children.length; 177 pv.Mark.prototype.index = -1; 178 179 /* 180 * Build each child, passing in the parent (this panel) scene graph node. The 181 * child mark's scene is initialized from the corresponding entry in the 182 * existing scene graph, such that properties from the previous build can be 183 * reused; this is largely to facilitate the recycling of SVG elements. 184 */ 185 for (var i = 0; i < n; i++) { 186 child = this.children[i]; 187 child.scene = s.children[i]; // possibly undefined 188 child.scale = scale; 189 child.build(); 190 } 191 192 /* 193 * Once the child marks have been built, the new scene graph nodes are removed 194 * from the child marks and placed into the scene graph. The nodes cannot 195 * remain on the child nodes because this panel (or a parent panel) may be 196 * instantiated multiple times! 197 */ 198 for (var i = 0; i < n; i++) { 199 child = this.children[i]; 200 s.children[i] = child.scene; 201 delete child.scene; 202 delete child.scale; 203 } 204 205 /* Delete any expired child scenes. */ 206 s.children.length = n; 207 }; 208 209 /** 210 * @private Computes the implied properties for this panel for the specified 211 * instance <tt>s</tt> in the scene graph. Panels have two implied 212 * properties:<ul> 213 * 214 * <li>The <tt>canvas</tt> property references the DOM element, typically a DIV, 215 * that contains the SVG element that is used to display the visualization. This 216 * property may be specified as a string, referring to the unique ID of the 217 * element in the DOM. The string is converted to a reference to the DOM 218 * element. The width and height of the SVG element is inferred from this DOM 219 * element. If no canvas property is specified, a new SVG element is created and 220 * inserted into the document, using the panel dimensions; see 221 * {@link #createCanvas}. 222 * 223 * <li>The <tt>children</tt> array, while not a property per se, contains the 224 * scene graph for each child mark. This array is initialized to be empty, and 225 * is populated above in {@link #buildInstance}. 226 * 227 * </ul>The current implementation creates the SVG element, if necessary, during 228 * the build phase; in the future, it may be preferrable to move this to the 229 * update phase, although then the canvas property would be undefined. In 230 * addition, DOM inspection is necessary to define the implied width and height 231 * properties that may be inferred from the DOM. 232 * 233 * @param s a node in the scene graph; the instance of the panel to build. 234 */ 235 pv.Panel.prototype.buildImplied = function(s) { 236 if (!this.parent) { 237 var c = s.canvas; 238 if (c) { 239 /* Clear the container if it's not associated with this panel. */ 240 if (c.$panel != this) { 241 c.$panel = this; 242 while (c.lastChild) c.removeChild(c.lastChild); 243 } 244 245 /* If width and height weren't specified, inspect the container. */ 246 var w, h; 247 if (s.width == null) { 248 w = parseFloat(pv.css(c, "width")); 249 s.width = w - s.left - s.right; 250 } 251 if (s.height == null) { 252 h = parseFloat(pv.css(c, "height")); 253 s.height = h - s.top - s.bottom; 254 } 255 } else { 256 var cache = this.$canvas || (this.$canvas = []); 257 if (!(c = cache[this.index])) { 258 c = cache[this.index] = document.createElement("span"); 259 if (this.$dom) { // script element for text/javascript+protovis 260 this.$dom.parentNode.insertBefore(c, this.$dom); 261 } else { // find the last element in the body 262 var n = document.body; 263 while (n.lastChild && n.lastChild.tagName) n = n.lastChild; 264 if (n != document.body) n = n.parentNode; 265 n.appendChild(c); 266 } 267 } 268 } 269 s.canvas = c; 270 } 271 if (!s.transform) s.transform = pv.Transform.identity; 272 pv.Mark.prototype.buildImplied.call(this, s); 273 }; 274