Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / docs / source / Svg.html
1 <!DOCTYPE html><html><head><title>Sencha Documentation Project</title><link rel="stylesheet" href="../reset.css" type="text/css"><link rel="stylesheet" href="../prettify.css" type="text/css"><link rel="stylesheet" href="../prettify_sa.css" type="text/css"><script type="text/javascript" src="../prettify.js"></script></head><body onload="prettyPrint()"><pre class="prettyprint"><pre><span id='Ext-draw.engine.Svg'>/**
2 </span> * @class Ext.draw.engine.Svg
3  * @extends Ext.draw.Surface
4  * Provides specific methods to draw with SVG.
5  */
6 Ext.define('Ext.draw.engine.Svg', {
7
8     /* Begin Definitions */
9
10     extend: 'Ext.draw.Surface',
11
12     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
13
14     /* End Definitions */
15
16     engine: 'Svg',
17
18     trimRe: /^\s+|\s+$/g,
19     spacesRe: /\s+/,
20     xlink: &quot;http:/&quot; + &quot;/www.w3.org/1999/xlink&quot;,
21
22     translateAttrs: {
23         radius: &quot;r&quot;,
24         radiusX: &quot;rx&quot;,
25         radiusY: &quot;ry&quot;,
26         path: &quot;d&quot;,
27         lineWidth: &quot;stroke-width&quot;,
28         fillOpacity: &quot;fill-opacity&quot;,
29         strokeOpacity: &quot;stroke-opacity&quot;,
30         strokeLinejoin: &quot;stroke-linejoin&quot;
31     },
32
33     minDefaults: {
34         circle: {
35             cx: 0,
36             cy: 0,
37             r: 0,
38             fill: &quot;none&quot;,
39             stroke: null,
40             &quot;stroke-width&quot;: null,
41             opacity: null,
42             &quot;fill-opacity&quot;: null,
43             &quot;stroke-opacity&quot;: null
44         },
45         ellipse: {
46             cx: 0,
47             cy: 0,
48             rx: 0,
49             ry: 0,
50             fill: &quot;none&quot;,
51             stroke: null,
52             &quot;stroke-width&quot;: null,
53             opacity: null,
54             &quot;fill-opacity&quot;: null,
55             &quot;stroke-opacity&quot;: null
56         },
57         rect: {
58             x: 0,
59             y: 0,
60             width: 0,
61             height: 0,
62             rx: 0,
63             ry: 0,
64             fill: &quot;none&quot;,
65             stroke: null,
66             &quot;stroke-width&quot;: null,
67             opacity: null,
68             &quot;fill-opacity&quot;: null,
69             &quot;stroke-opacity&quot;: null
70         },
71         text: {
72             x: 0,
73             y: 0,
74             &quot;text-anchor&quot;: &quot;start&quot;,
75             &quot;font-family&quot;: null,
76             &quot;font-size&quot;: null,
77             &quot;font-weight&quot;: null,
78             &quot;font-style&quot;: null,
79             fill: &quot;#000&quot;,
80             stroke: null,
81             &quot;stroke-width&quot;: null,
82             opacity: null,
83             &quot;fill-opacity&quot;: null,
84             &quot;stroke-opacity&quot;: null
85         },
86         path: {
87             d: &quot;M0,0&quot;,
88             fill: &quot;none&quot;,
89             stroke: null,
90             &quot;stroke-width&quot;: null,
91             opacity: null,
92             &quot;fill-opacity&quot;: null,
93             &quot;stroke-opacity&quot;: null
94         },
95         image: {
96             x: 0,
97             y: 0,
98             width: 0,
99             height: 0,
100             preserveAspectRatio: &quot;none&quot;,
101             opacity: null
102         }
103     },
104
105     createSvgElement: function(type, attrs) {
106         var el = this.domRef.createElementNS(&quot;http:/&quot; + &quot;/www.w3.org/2000/svg&quot;, type),
107             key;
108         if (attrs) {
109             for (key in attrs) {
110                 el.setAttribute(key, String(attrs[key]));
111             }
112         }
113         return el;
114     },
115
116     createSpriteElement: function(sprite) {
117         // Create svg element and append to the DOM.
118         var el = this.createSvgElement(sprite.type);
119         el.id = sprite.id;
120         if (el.style) {
121             el.style.webkitTapHighlightColor = &quot;rgba(0,0,0,0)&quot;;
122         }
123         sprite.el = Ext.get(el);
124         this.applyZIndex(sprite); //performs the insertion
125         sprite.matrix = Ext.create('Ext.draw.Matrix');
126         sprite.bbox = {
127             plain: 0,
128             transform: 0
129         };
130         sprite.fireEvent(&quot;render&quot;, sprite);
131         return el;
132     },
133
134     getBBox: function (sprite, isWithoutTransform) {
135         var realPath = this[&quot;getPath&quot; + sprite.type](sprite);
136         if (isWithoutTransform) {
137             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
138             return sprite.bbox.plain;
139         }
140         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
141         return sprite.bbox.transform;
142     },
143     
144     getBBoxText: function (sprite) {
145         var bbox = {},
146             bb, height, width, i, ln, el;
147
148         if (sprite &amp;&amp; sprite.el) {
149             el = sprite.el.dom;
150             try {
151                 bbox = el.getBBox();
152                 return bbox;
153             } catch(e) {
154                 // Firefox 3.0.x plays badly here
155             }
156             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
157             ln = el.getNumberOfChars();
158             for (i = 0; i &lt; ln; i++) {
159                 bb = el.getExtentOfChar(i);
160                 bbox.y = Math.min(bb.y, bbox.y);
161                 height = bb.y + bb.height - bbox.y;
162                 bbox.height = Math.max(bbox.height, height);
163                 width = bb.x + bb.width - bbox.x;
164                 bbox.width = Math.max(bbox.width, width);
165             }
166             return bbox;
167         }
168     },
169
170     hide: function() {
171         Ext.get(this.el).hide();
172     },
173
174     show: function() {
175         Ext.get(this.el).show();
176     },
177
178     hidePrim: function(sprite) {
179         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
180     },
181
182     showPrim: function(sprite) {
183         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
184     },
185
186     getDefs: function() {
187         return this._defs || (this._defs = this.createSvgElement(&quot;defs&quot;));
188     },
189
190     transform: function(sprite) {
191         var me = this,
192             matrix = Ext.create('Ext.draw.Matrix'),
193             transforms = sprite.transformations,
194             transformsLength = transforms.length,
195             i = 0,
196             transform, type;
197             
198         for (; i &lt; transformsLength; i++) {
199             transform = transforms[i];
200             type = transform.type;
201             if (type == &quot;translate&quot;) {
202                 matrix.translate(transform.x, transform.y);
203             }
204             else if (type == &quot;rotate&quot;) {
205                 matrix.rotate(transform.degrees, transform.x, transform.y);
206             }
207             else if (type == &quot;scale&quot;) {
208                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
209             }
210         }
211         sprite.matrix = matrix;
212         sprite.el.set({transform: matrix.toSvg()});
213     },
214
215     setSize: function(w, h) {
216         var me = this,
217             el = me.el;
218         
219         w = +w || me.width;
220         h = +h || me.height;
221         me.width = w;
222         me.height = h;
223
224         el.setSize(w, h);
225         el.set({
226             width: w,
227             height: h
228         });
229         me.callParent([w, h]);
230     },
231
232 <span id='Ext-draw.engine.Svg-method-getRegion'>    /**
233 </span>     * Get the region for the surface's canvas area
234      * @returns {Ext.util.Region}
235      */
236     getRegion: function() {
237         // Mozilla requires using the background rect because the svg element returns an
238         // incorrect region. Webkit gives no region for the rect and must use the svg element.
239         var svgXY = this.el.getXY(),
240             rectXY = this.bgRect.getXY(),
241             max = Math.max,
242             x = max(svgXY[0], rectXY[0]),
243             y = max(svgXY[1], rectXY[1]);
244         return {
245             left: x,
246             top: y,
247             right: x + this.width,
248             bottom: y + this.height
249         };
250     },
251
252     onRemove: function(sprite) {
253         if (sprite.el) {
254             sprite.el.remove();
255             delete sprite.el;
256         }
257         this.callParent(arguments);
258     },
259     
260     setViewBox: function(x, y, width, height) {
261         if (isFinite(x) &amp;&amp; isFinite(y) &amp;&amp; isFinite(width) &amp;&amp; isFinite(height)) {
262             this.callParent(arguments);
263             this.el.dom.setAttribute(&quot;viewBox&quot;, [x, y, width, height].join(&quot; &quot;));
264         }
265     },
266
267     render: function (container) {
268         var me = this;
269         if (!me.el) {
270             var width = me.width || 10,
271                 height = me.height || 10,
272                 el = me.createSvgElement('svg', {
273                     xmlns: &quot;http:/&quot; + &quot;/www.w3.org/2000/svg&quot;,
274                     version: 1.1,
275                     width: width,
276                     height: height
277                 }),
278                 defs = me.getDefs(),
279
280                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
281                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
282                 // use it rather than the svg element for retrieving the correct client rect of the
283                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
284                 bgRect = me.createSvgElement(&quot;rect&quot;, {
285                     width: &quot;100%&quot;,
286                     height: &quot;100%&quot;,
287                     fill: &quot;#000&quot;,
288                     stroke: &quot;none&quot;,
289                     opacity: 0
290                 }),
291                 webkitRect;
292             
293                 if (Ext.isSafari3) {
294                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
295                     webkitRect = me.createSvgElement(&quot;rect&quot;, {
296                         x: -10,
297                         y: -10,
298                         width: &quot;110%&quot;,
299                         height: &quot;110%&quot;,
300                         fill: &quot;none&quot;,
301                         stroke: &quot;#000&quot;
302                     });
303                 }
304             el.appendChild(defs);
305             if (Ext.isSafari3) {
306                 el.appendChild(webkitRect);
307             }
308             el.appendChild(bgRect);
309             container.appendChild(el);
310             me.el = Ext.get(el);
311             me.bgRect = Ext.get(bgRect);
312             if (Ext.isSafari3) {
313                 me.webkitRect = Ext.get(webkitRect);
314                 me.webkitRect.hide();
315             }
316             me.el.on({
317                 scope: me,
318                 mouseup: me.onMouseUp,
319                 mousedown: me.onMouseDown,
320                 mouseover: me.onMouseOver,
321                 mouseout: me.onMouseOut,
322                 mousemove: me.onMouseMove,
323                 mouseenter: me.onMouseEnter,
324                 mouseleave: me.onMouseLeave,
325                 click: me.onClick
326             });
327         }
328         me.renderAll();
329     },
330
331     // private
332     onMouseEnter: function(e) {
333         if (this.el.parent().getRegion().contains(e.getPoint())) {
334             this.fireEvent('mouseenter', e);
335         }
336     },
337
338     // private
339     onMouseLeave: function(e) {
340         if (!this.el.parent().getRegion().contains(e.getPoint())) {
341             this.fireEvent('mouseleave', e);
342         }
343     },
344     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
345     processEvent: function(name, e) {
346         var target = e.getTarget(),
347             surface = this.surface,
348             sprite;
349
350         this.fireEvent(name, e);
351         // We wrap text types in a tspan, sprite is the parent.
352         if (target.nodeName == &quot;tspan&quot; &amp;&amp; target.parentNode) {
353             target = target.parentNode;
354         }
355         sprite = this.items.get(target.id);
356         if (sprite) {
357             sprite.fireEvent(name, sprite, e);
358         }
359     },
360
361     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
362      * the baseline for text the vertical middle of the text to be the same as VML.
363      */
364     tuneText: function (sprite, attrs) {
365         var el = sprite.el.dom,
366             tspans = [],
367             height, tspan, text, i, ln, texts, factor;
368
369         if (attrs.hasOwnProperty(&quot;text&quot;)) {
370            tspans = this.setText(sprite, attrs.text);
371         }
372         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
373         if (tspans.length) {
374             height = this.getBBoxText(sprite).height;
375             for (i = 0, ln = tspans.length; i &lt; ln; i++) {
376                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
377                 // so we are going to normalize that here
378                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
379                 tspans[i].setAttribute(&quot;dy&quot;, i ? height * 1.2 : height / factor);
380             }
381             sprite.dirty = true;
382         }
383     },
384
385     setText: function(sprite, textString) {
386          var me = this,
387              el = sprite.el.dom,
388              x = el.getAttribute(&quot;x&quot;),
389              tspans = [],
390              height, tspan, text, i, ln, texts;
391         
392         while (el.firstChild) {
393             el.removeChild(el.firstChild);
394         }
395         // Wrap each row into tspan to emulate rows
396         texts = String(textString).split(&quot;\n&quot;);
397         for (i = 0, ln = texts.length; i &lt; ln; i++) {
398             text = texts[i];
399             if (text) {
400                 tspan = me.createSvgElement(&quot;tspan&quot;);
401                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
402                 tspan.setAttribute(&quot;x&quot;, x);
403                 el.appendChild(tspan);
404                 tspans[i] = tspan;
405             }
406         }
407         return tspans;
408     },
409
410     renderAll: function() {
411         this.items.each(this.renderItem, this);
412     },
413
414     renderItem: function (sprite) {
415         if (!this.el) {
416             return;
417         }
418         if (!sprite.el) {
419             this.createSpriteElement(sprite);
420         }
421         if (sprite.zIndexDirty) {
422             this.applyZIndex(sprite);
423         }
424         if (sprite.dirty) {
425             this.applyAttrs(sprite);
426             this.applyTransformations(sprite);
427         }
428     },
429
430     redraw: function(sprite) {
431         sprite.dirty = sprite.zIndexDirty = true;
432         this.renderItem(sprite);
433     },
434
435     applyAttrs: function (sprite) {
436         var me = this,
437             el = sprite.el,
438             group = sprite.group,
439             sattr = sprite.attr,
440             groups, i, ln, attrs, font, key, style, name, rect;
441
442         if (group) {
443             groups = [].concat(group);
444             ln = groups.length;
445             for (i = 0; i &lt; ln; i++) {
446                 group = groups[i];
447                 me.getGroup(group).add(sprite);
448             }
449             delete sprite.group;
450         }
451         attrs = me.scrubAttrs(sprite) || {};
452
453         // if (sprite.dirtyPath) {
454         sprite.bbox.plain = 0;
455         sprite.bbox.transform = 0;
456             if (sprite.type == &quot;circle&quot; || sprite.type == &quot;ellipse&quot;) {
457                 attrs.cx = attrs.cx || attrs.x;
458                 attrs.cy = attrs.cy || attrs.y;
459             }
460             else if (sprite.type == &quot;rect&quot;) {
461                 attrs.rx = attrs.ry = attrs.r;
462             }
463             else if (sprite.type == &quot;path&quot; &amp;&amp; attrs.d) {
464                 attrs.d = Ext.draw.Draw.pathToAbsolute(attrs.d);
465             }
466             sprite.dirtyPath = false;
467         // }
468
469         if (attrs['clip-rect']) {
470             me.setClip(sprite, attrs);
471             delete attrs['clip-rect'];
472         }
473         if (sprite.type == 'text' &amp;&amp; attrs.font &amp;&amp; sprite.dirtyFont) {
474             el.set({ style: &quot;font: &quot; + attrs.font});
475             sprite.dirtyFont = false;
476         }
477         if (sprite.type == &quot;image&quot;) {
478             el.dom.setAttributeNS(me.xlink, &quot;href&quot;, attrs.src);
479         }
480         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
481
482         if (sprite.dirtyHidden) {
483             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
484             sprite.dirtyHidden = false;
485         }
486         for (key in attrs) {
487             if (attrs.hasOwnProperty(key) &amp;&amp; attrs[key] != null) {
488                 el.dom.setAttribute(key, String(attrs[key]));
489             }
490         }
491         if (sprite.type == 'text') {
492             me.tuneText(sprite, attrs);
493         }
494
495         //set styles
496         style = sattr.style;
497         if (style) {
498             el.setStyle(style);
499         }
500
501         sprite.dirty = false;
502
503         if (Ext.isSafari3) {
504             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
505             me.webkitRect.show();
506             setTimeout(function () {
507                 me.webkitRect.hide();
508             });
509         }
510     },
511
512     setClip: function(sprite, params) {
513         var me = this,
514             rect = params[&quot;clip-rect&quot;],
515             clipEl, clipPath;
516         if (rect) {
517             if (sprite.clip) {
518                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
519             }
520             clipEl = me.createSvgElement('clipPath');
521             clipPath = me.createSvgElement('rect');
522             clipEl.id = Ext.id(null, 'ext-clip-');
523             clipPath.setAttribute(&quot;x&quot;, rect.x);
524             clipPath.setAttribute(&quot;y&quot;, rect.y);
525             clipPath.setAttribute(&quot;width&quot;, rect.width);
526             clipPath.setAttribute(&quot;height&quot;, rect.height);
527             clipEl.appendChild(clipPath);
528             me.getDefs().appendChild(clipEl);
529             sprite.el.dom.setAttribute(&quot;clip-path&quot;, &quot;url(#&quot; + clipEl.id + &quot;)&quot;);
530             sprite.clip = clipPath;
531         }
532         // if (!attrs[key]) {
533         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute(&quot;clip-path&quot;).replace(/(^url\(#|\)$)/g, &quot;&quot;));
534         //     clip &amp;&amp; clip.parentNode.removeChild(clip);
535         //     sprite.el.setAttribute(&quot;clip-path&quot;, &quot;&quot;);
536         //     delete attrss.clip;
537         // }
538     },
539
540 <span id='Ext-draw.engine.Svg-method-applyZIndex'>    /**
541 </span>     * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
542      * @param {Ext.draw.Sprite} sprite
543      */
544     applyZIndex: function(sprite) {
545         var idx = this.normalizeSpriteCollection(sprite),
546             el = sprite.el,
547             prevEl;
548         if (this.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect 
549             if (idx &gt; 0) {
550                 // Find the first previous sprite which has its DOM element created already
551                 do {
552                     prevEl = this.items.getAt(--idx).el;
553                 } while (!prevEl &amp;&amp; idx &gt; 0);
554             }
555             el.insertAfter(prevEl || this.bgRect);
556         }
557         sprite.zIndexDirty = false;
558     },
559
560     createItem: function (config) {
561         var sprite = Ext.create('Ext.draw.Sprite', config);
562         sprite.surface = this;
563         return sprite;
564     },
565
566     addGradient: function(gradient) {
567         gradient = Ext.draw.Draw.parseGradient(gradient);
568         var ln = gradient.stops.length,
569             vector = gradient.vector,
570             gradientEl,
571             stop,
572             stopEl,
573             i;
574         if (gradient.type == &quot;linear&quot;) {
575             gradientEl = this.createSvgElement(&quot;linearGradient&quot;);
576             gradientEl.setAttribute(&quot;x1&quot;, vector[0]);
577             gradientEl.setAttribute(&quot;y1&quot;, vector[1]);
578             gradientEl.setAttribute(&quot;x2&quot;, vector[2]);
579             gradientEl.setAttribute(&quot;y2&quot;, vector[3]);
580         }
581         else {
582             gradientEl = this.createSvgElement(&quot;radialGradient&quot;);
583             gradientEl.setAttribute(&quot;cx&quot;, gradient.centerX);
584             gradientEl.setAttribute(&quot;cy&quot;, gradient.centerY);
585             gradientEl.setAttribute(&quot;r&quot;, gradient.radius);
586             if (Ext.isNumber(gradient.focalX) &amp;&amp; Ext.isNumber(gradient.focalY)) {
587                 gradientEl.setAttribute(&quot;fx&quot;, gradient.focalX);
588                 gradientEl.setAttribute(&quot;fy&quot;, gradient.focalY);
589             }
590         }    
591         gradientEl.id = gradient.id;
592         this.getDefs().appendChild(gradientEl);
593
594         for (i = 0; i &lt; ln; i++) {
595             stop = gradient.stops[i];
596             stopEl = this.createSvgElement(&quot;stop&quot;);
597             stopEl.setAttribute(&quot;offset&quot;, stop.offset + &quot;%&quot;);
598             stopEl.setAttribute(&quot;stop-color&quot;, stop.color);
599             stopEl.setAttribute(&quot;stop-opacity&quot;,stop.opacity);
600             gradientEl.appendChild(stopEl);
601         }
602     },
603
604 <span id='Ext-draw.engine.Svg-method-hasCls'>    /**
605 </span>     * Checks if the specified CSS class exists on this element's DOM node.
606      * @param {String} className The CSS class to check for
607      * @return {Boolean} True if the class exists, else false
608      */
609     hasCls: function(sprite, className) {
610         return className &amp;&amp; (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
611     },
612
613     addCls: function(sprite, className) {
614         var el = sprite.el,
615             i,
616             len,
617             v,
618             cls = [],
619             curCls =  el.getAttribute('class') || '';
620         // Separate case is for speed
621         if (!Ext.isArray(className)) {
622             if (typeof className == 'string' &amp;&amp; !this.hasCls(sprite, className)) {
623                 el.set({ 'class': curCls + ' ' + className });
624             }
625         }
626         else {
627             for (i = 0, len = className.length; i &lt; len; i++) {
628                 v = className[i];
629                 if (typeof v == 'string' &amp;&amp; (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
630                     cls.push(v);
631                 }
632             }
633             if (cls.length) {
634                 el.set({ 'class': ' ' + cls.join(' ') });
635             }
636         }
637     },
638
639     removeCls: function(sprite, className) {
640         var me = this,
641             el = sprite.el,
642             curCls =  el.getAttribute('class') || '',
643             i, idx, len, cls, elClasses;
644         if (!Ext.isArray(className)){
645             className = [className];
646         }
647         if (curCls) {
648             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
649             for (i = 0, len = className.length; i &lt; len; i++) {
650                 cls = className[i];
651                 if (typeof cls == 'string') {
652                     cls = cls.replace(me.trimRe, '');
653                     idx = Ext.Array.indexOf(elClasses, cls);
654                     if (idx != -1) {
655                         elClasses.splice(idx, 1);
656                     }
657                 }
658             }
659             el.set({ 'class': elClasses.join(' ') });
660         }
661     },
662
663     destroy: function() {
664         var me = this;
665         
666         me.callParent();
667         if (me.el) {
668             me.el.remove();
669         }
670         delete me.el;
671     }
672 });</pre></pre></body></html>