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