1 /**
  2  * @private
  3  * @namespace
  4  */
  5 pv.Scene = pv.SvgScene = {
  6   /* Various namespaces. */
  7   svg: "http://www.w3.org/2000/svg",
  8   xmlns: "http://www.w3.org/2000/xmlns",
  9   xlink: "http://www.w3.org/1999/xlink",
 10   xhtml: "http://www.w3.org/1999/xhtml",
 11 
 12   /** The pre-multipled scale, based on any enclosing transforms. */
 13   scale: 1,
 14 
 15   /** The set of supported events. */
 16   events: [
 17     "DOMMouseScroll", // for Firefox
 18     "mousewheel",
 19     "mousedown",
 20     "mouseup",
 21     "mouseover",
 22     "mouseout",
 23     "mousemove",
 24     "click",
 25     "dblclick"
 26   ],
 27 
 28   /** Implicit values for SVG and CSS properties. */
 29   implicit: {
 30     svg: {
 31       "shape-rendering": "auto",
 32       "pointer-events": "painted",
 33       "x": 0,
 34       "y": 0,
 35       "dy": 0,
 36       "text-anchor": "start",
 37       "transform": "translate(0,0)",
 38       "fill": "none",
 39       "fill-opacity": 1,
 40       "stroke": "none",
 41       "stroke-opacity": 1,
 42       "stroke-width": 1.5,
 43       "stroke-linejoin": "miter"
 44     },
 45     css: {
 46       "font": "10px sans-serif"
 47     }
 48   }
 49 };
 50 
 51 /**
 52  * Updates the display for the specified array of scene nodes.
 53  *
 54  * @param scenes {array} an array of scene nodes.
 55  */
 56 pv.SvgScene.updateAll = function(scenes) {
 57   if (scenes.length
 58       && scenes[0].reverse
 59       && (scenes.type != "line")
 60       && (scenes.type != "area")) {
 61     var reversed = pv.extend(scenes);
 62     for (var i = 0, j = scenes.length - 1; j >= 0; i++, j--) {
 63       reversed[i] = scenes[j];
 64     }
 65     scenes = reversed;
 66   }
 67   this.removeSiblings(this[scenes.type](scenes));
 68 };
 69 
 70 /**
 71  * Creates a new SVG element of the specified type.
 72  *
 73  * @param type {string} an SVG element type, such as "rect".
 74  * @returns a new SVG element.
 75  */
 76 pv.SvgScene.create = function(type) {
 77   return document.createElementNS(this.svg, type);
 78 };
 79 
 80 /**
 81  * Expects the element <i>e</i> to be the specified type. If the element does
 82  * not exist, a new one is created. If the element does exist but is the wrong
 83  * type, it is replaced with the specified element.
 84  *
 85  * @param e the current SVG element.
 86  * @param type {string} an SVG element type, such as "rect".
 87  * @param attributes an optional attribute map.
 88  * @param style an optional style map.
 89  * @returns a new SVG element.
 90  */
 91 pv.SvgScene.expect = function(e, type, attributes, style) {
 92   if (e) {
 93     if (e.tagName == "a") e = e.firstChild;
 94     if (e.tagName != type) {
 95       var n = this.create(type);
 96       e.parentNode.replaceChild(n, e);
 97       e = n;
 98     }
 99   } else {
100     e = this.create(type);
101   }
102   for (var name in attributes) {
103     var value = attributes[name];
104     if (value == this.implicit.svg[name]) value = null;
105     if (value == null) e.removeAttribute(name);
106     else e.setAttribute(name, value);
107   }
108   for (var name in style) {
109     var value = style[name];
110     if (value == this.implicit.css[name]) value = null;
111     if (value == null) e.style.removeProperty(name);
112     else e.style[name] = value;
113   }
114   return e;
115 };
116 
117 /** TODO */
118 pv.SvgScene.append = function(e, scenes, index) {
119   e.$scene = {scenes:scenes, index:index};
120   e = this.title(e, scenes[index]);
121   if (!e.parentNode) scenes.$g.appendChild(e);
122   return e.nextSibling;
123 };
124 
125 /**
126  * Applies a title tooltip to the specified element <tt>e</tt>, using the
127  * <tt>title</tt> property of the specified scene node <tt>s</tt>. Note that
128  * this implementation does not create an SVG <tt>title</tt> element as a child
129  * of <tt>e</tt>; although this is the recommended standard, it is only
130  * supported in Opera. Instead, an anchor element is created around the element
131  * <tt>e</tt>, and the <tt>xlink:title</tt> attribute is set accordingly.
132  *
133  * @param e an SVG element.
134  * @param s a scene node.
135  */
136 pv.SvgScene.title = function(e, s) {
137   var a = e.parentNode;
138   if (a && (a.tagName != "a")) a = null;
139   if (s.title) {
140     if (!a) {
141       a = this.create("a");
142       if (e.parentNode) e.parentNode.replaceChild(a, e);
143       a.appendChild(e);
144     }
145     a.setAttributeNS(this.xlink, "title", s.title);
146     return a;
147   }
148   if (a) a.parentNode.replaceChild(e, a);
149   return e;
150 };
151 
152 /** TODO */
153 pv.SvgScene.dispatch = pv.listener(function(e) {
154   var t = e.target.$scene;
155   if (t) {
156     var type = e.type;
157 
158     /* Fixes for mousewheel support on Firefox & Opera. */
159     switch (type) {
160       case "DOMMouseScroll": {
161         type = "mousewheel";
162         e.wheel = -480 * e.detail;
163         break;
164       }
165       case "mousewheel": {
166         e.wheel = (window.opera ? 12 : 1) * e.wheelDelta;
167         break;
168       }
169     }
170 
171     if (pv.Mark.dispatch(type, t.scenes, t.index)) e.preventDefault();
172   }
173 });
174 
175 /** @private Remove siblings following element <i>e</i>. */
176 pv.SvgScene.removeSiblings = function(e) {
177   while (e) {
178     var n = e.nextSibling;
179     e.parentNode.removeChild(e);
180     e = n;
181   }
182 };
183 
184 /** @private Do nothing when rendering undefined mark types. */
185 pv.SvgScene.undefined = function() {};
186