4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-draw-engine-Svg'>/**
19 </span> * @class Ext.draw.engine.Svg
20 * @extends Ext.draw.Surface
21 * Provides specific methods to draw with SVG.
23 Ext.define('Ext.draw.engine.Svg', {
25 /* Begin Definitions */
27 extend: 'Ext.draw.Surface',
29 requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.core.Element'],
37 xlink: "http:/" + "/www.w3.org/1999/xlink",
40 radius: "r",
41 radiusX: "rx",
42 radiusY: "ry",
44 lineWidth: "stroke-width",
45 fillOpacity: "fill-opacity",
46 strokeOpacity: "stroke-opacity",
47 strokeLinejoin: "stroke-linejoin"
57 fill: "none",
59 "stroke-width": null,
61 "fill-opacity": null,
62 "stroke-opacity": null
69 fill: "none",
71 "stroke-width": null,
73 "fill-opacity": null,
74 "stroke-opacity": null
83 fill: "none",
85 "stroke-width": null,
87 "fill-opacity": null,
88 "stroke-opacity": null
93 "text-anchor": "start",
94 "font-family": null,
95 "font-size": null,
96 "font-weight": null,
97 "font-style": null,
98 fill: "#000",
100 "stroke-width": null,
102 "fill-opacity": null,
103 "stroke-opacity": null
107 fill: "none",
109 "stroke-width": null,
111 "fill-opacity": null,
112 "stroke-opacity": null
119 preserveAspectRatio: "none",
124 createSvgElement: function(type, attrs) {
125 var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
129 el.setAttribute(key, String(attrs[key]));
135 createSpriteElement: function(sprite) {
136 // Create svg element and append to the DOM.
137 var el = this.createSvgElement(sprite.type);
140 el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
142 sprite.el = Ext.get(el);
143 this.applyZIndex(sprite); //performs the insertion
144 sprite.matrix = Ext.create('Ext.draw.Matrix');
149 sprite.fireEvent("render", sprite);
153 getBBox: function (sprite, isWithoutTransform) {
154 var realPath = this["getPath" + sprite.type](sprite);
155 if (isWithoutTransform) {
156 sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
157 return sprite.bbox.plain;
159 sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
160 return sprite.bbox.transform;
163 getBBoxText: function (sprite) {
165 bb, height, width, i, ln, el;
167 if (sprite && sprite.el) {
173 // Firefox 3.0.x plays badly here
175 bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
176 ln = el.getNumberOfChars();
177 for (i = 0; i < ln; i++) {
178 bb = el.getExtentOfChar(i);
179 bbox.y = Math.min(bb.y, bbox.y);
180 height = bb.y + bb.height - bbox.y;
181 bbox.height = Math.max(bbox.height, height);
182 width = bb.x + bb.width - bbox.x;
183 bbox.width = Math.max(bbox.width, width);
190 Ext.get(this.el).hide();
194 Ext.get(this.el).show();
197 hidePrim: function(sprite) {
198 this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
201 showPrim: function(sprite) {
202 this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
205 getDefs: function() {
206 return this._defs || (this._defs = this.createSvgElement("defs"));
209 transform: function(sprite) {
211 matrix = Ext.create('Ext.draw.Matrix'),
212 transforms = sprite.transformations,
213 transformsLength = transforms.length,
217 for (; i < transformsLength; i++) {
218 transform = transforms[i];
219 type = transform.type;
220 if (type == "translate") {
221 matrix.translate(transform.x, transform.y);
223 else if (type == "rotate") {
224 matrix.rotate(transform.degrees, transform.x, transform.y);
226 else if (type == "scale") {
227 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
230 sprite.matrix = matrix;
231 sprite.el.set({transform: matrix.toSvg()});
234 setSize: function(w, h) {
248 me.callParent([w, h]);
251 <span id='Ext-draw-engine-Svg-method-getRegion'> /**
252 </span> * Get the region for the surface's canvas area
253 * @returns {Ext.util.Region}
255 getRegion: function() {
256 // Mozilla requires using the background rect because the svg element returns an
257 // incorrect region. Webkit gives no region for the rect and must use the svg element.
258 var svgXY = this.el.getXY(),
259 rectXY = this.bgRect.getXY(),
261 x = max(svgXY[0], rectXY[0]),
262 y = max(svgXY[1], rectXY[1]);
266 right: x + this.width,
267 bottom: y + this.height
271 onRemove: function(sprite) {
276 this.callParent(arguments);
279 setViewBox: function(x, y, width, height) {
280 if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
281 this.callParent(arguments);
282 this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
286 render: function (container) {
289 var width = me.width || 10,
290 height = me.height || 10,
291 el = me.createSvgElement('svg', {
292 xmlns: "http:/" + "/www.w3.org/2000/svg",
299 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
300 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
301 // use it rather than the svg element for retrieving the correct client rect of the
302 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
303 bgRect = me.createSvgElement("rect", {
304 width: "100%",
305 height: "100%",
306 fill: "#000",
307 stroke: "none",
313 // Rect that we will show/hide to fix old WebKit bug with rendering issues.
314 webkitRect = me.createSvgElement("rect", {
317 width: "110%",
318 height: "110%",
319 fill: "none",
320 stroke: "#000"
323 el.appendChild(defs);
325 el.appendChild(webkitRect);
327 el.appendChild(bgRect);
328 container.appendChild(el);
330 me.bgRect = Ext.get(bgRect);
332 me.webkitRect = Ext.get(webkitRect);
333 me.webkitRect.hide();
337 mouseup: me.onMouseUp,
338 mousedown: me.onMouseDown,
339 mouseover: me.onMouseOver,
340 mouseout: me.onMouseOut,
341 mousemove: me.onMouseMove,
342 mouseenter: me.onMouseEnter,
343 mouseleave: me.onMouseLeave,
351 onMouseEnter: function(e) {
352 if (this.el.parent().getRegion().contains(e.getPoint())) {
353 this.fireEvent('mouseenter', e);
358 onMouseLeave: function(e) {
359 if (!this.el.parent().getRegion().contains(e.getPoint())) {
360 this.fireEvent('mouseleave', e);
363 // @private - Normalize a delegated single event from the main container to each sprite and sprite group
364 processEvent: function(name, e) {
365 var target = e.getTarget(),
366 surface = this.surface,
369 this.fireEvent(name, e);
370 // We wrap text types in a tspan, sprite is the parent.
371 if (target.nodeName == "tspan" && target.parentNode) {
372 target = target.parentNode;
374 sprite = this.items.get(target.id);
376 sprite.fireEvent(name, sprite, e);
380 /* @private - Wrap SVG text inside a tspan to allow for line wrapping. In addition this normallizes
381 * the baseline for text the vertical middle of the text to be the same as VML.
383 tuneText: function (sprite, attrs) {
384 var el = sprite.el.dom,
386 height, tspan, text, i, ln, texts, factor;
388 if (attrs.hasOwnProperty("text")) {
389 tspans = this.setText(sprite, attrs.text);
391 // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
393 height = this.getBBoxText(sprite).height;
394 for (i = 0, ln = tspans.length; i < ln; i++) {
395 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
396 // so we are going to normalize that here
397 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
398 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
404 setText: function(sprite, textString) {
407 x = el.getAttribute("x"),
409 height, tspan, text, i, ln, texts;
411 while (el.firstChild) {
412 el.removeChild(el.firstChild);
414 // Wrap each row into tspan to emulate rows
415 texts = String(textString).split("\n");
416 for (i = 0, ln = texts.length; i < ln; i++) {
419 tspan = me.createSvgElement("tspan");
420 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
421 tspan.setAttribute("x", x);
422 el.appendChild(tspan);
429 renderAll: function() {
430 this.items.each(this.renderItem, this);
433 renderItem: function (sprite) {
438 this.createSpriteElement(sprite);
440 if (sprite.zIndexDirty) {
441 this.applyZIndex(sprite);
444 this.applyAttrs(sprite);
445 this.applyTransformations(sprite);
449 redraw: function(sprite) {
450 sprite.dirty = sprite.zIndexDirty = true;
451 this.renderItem(sprite);
454 applyAttrs: function (sprite) {
457 group = sprite.group,
459 parsers = me.parsers,
460 //Safari does not handle linear gradients correctly in quirksmode
461 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
463 gradientsMap = me.gradientsMap || {},
464 safariFix = Ext.isSafari && !Ext.isStrict,
465 groups, i, ln, attrs, font, key, style, name, rect;
468 groups = [].concat(group);
470 for (i = 0; i < ln; i++) {
472 me.getGroup(group).add(sprite);
476 attrs = me.scrubAttrs(sprite) || {};
478 // if (sprite.dirtyPath) {
479 sprite.bbox.plain = 0;
480 sprite.bbox.transform = 0;
481 if (sprite.type == "circle" || sprite.type == "ellipse") {
482 attrs.cx = attrs.cx || attrs.x;
483 attrs.cy = attrs.cy || attrs.y;
485 else if (sprite.type == "rect") {
486 attrs.rx = attrs.ry = attrs.r;
488 else if (sprite.type == "path" && attrs.d) {
489 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
491 sprite.dirtyPath = false;
497 if (attrs['clip-rect']) {
498 me.setClip(sprite, attrs);
499 delete attrs['clip-rect'];
501 if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
502 el.set({ style: "font: " + attrs.font});
503 sprite.dirtyFont = false;
505 if (sprite.type == "image") {
506 el.dom.setAttributeNS(me.xlink, "href", attrs.src);
508 Ext.applyIf(attrs, me.minDefaults[sprite.type]);
510 if (sprite.dirtyHidden) {
511 (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
512 sprite.dirtyHidden = false;
515 if (attrs.hasOwnProperty(key) && attrs[key] != null) {
516 //Safari does not handle linear gradients correctly in quirksmode
517 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
519 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
520 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
521 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
522 attrs[key] = gradientsMap[attrs[key]];
524 if (key in parsers) {
525 el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
527 el.dom.setAttribute(key, attrs[key]);
532 if (sprite.type == 'text') {
533 me.tuneText(sprite, attrs);
542 sprite.dirty = false;
545 // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
546 me.webkitRect.show();
547 setTimeout(function () {
548 me.webkitRect.hide();
553 setClip: function(sprite, params) {
555 rect = params["clip-rect"],
559 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
561 clipEl = me.createSvgElement('clipPath');
562 clipPath = me.createSvgElement('rect');
563 clipEl.id = Ext.id(null, 'ext-clip-');
564 clipPath.setAttribute("x", rect.x);
565 clipPath.setAttribute("y", rect.y);
566 clipPath.setAttribute("width", rect.width);
567 clipPath.setAttribute("height", rect.height);
568 clipEl.appendChild(clipPath);
569 me.getDefs().appendChild(clipEl);
570 sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
571 sprite.clip = clipPath;
573 // if (!attrs[key]) {
574 // var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
575 // clip && clip.parentNode.removeChild(clip);
576 // sprite.el.setAttribute("clip-path", "");
577 // delete attrss.clip;
581 <span id='Ext-draw-engine-Svg-method-applyZIndex'> /**
582 </span> * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
583 * @param {Ext.draw.Sprite} sprite
585 applyZIndex: function(sprite) {
586 var idx = this.normalizeSpriteCollection(sprite),
589 if (this.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
591 // Find the first previous sprite which has its DOM element created already
593 prevEl = this.items.getAt(--idx).el;
594 } while (!prevEl && idx > 0);
596 el.insertAfter(prevEl || this.bgRect);
598 sprite.zIndexDirty = false;
601 createItem: function (config) {
602 var sprite = Ext.create('Ext.draw.Sprite', config);
603 sprite.surface = this;
607 addGradient: function(gradient) {
608 gradient = Ext.draw.Draw.parseGradient(gradient);
610 ln = gradient.stops.length,
611 vector = gradient.vector,
612 //Safari does not handle linear gradients correctly in quirksmode
613 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
615 usePlain = Ext.isSafari && !Ext.isStrict,
616 gradientEl, stop, stopEl, i, gradientsMap;
618 gradientsMap = me.gradientsMap || {};
621 if (gradient.type == "linear") {
622 gradientEl = me.createSvgElement("linearGradient");
623 gradientEl.setAttribute("x1", vector[0]);
624 gradientEl.setAttribute("y1", vector[1]);
625 gradientEl.setAttribute("x2", vector[2]);
626 gradientEl.setAttribute("y2", vector[3]);
629 gradientEl = me.createSvgElement("radialGradient");
630 gradientEl.setAttribute("cx", gradient.centerX);
631 gradientEl.setAttribute("cy", gradient.centerY);
632 gradientEl.setAttribute("r", gradient.radius);
633 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
634 gradientEl.setAttribute("fx", gradient.focalX);
635 gradientEl.setAttribute("fy", gradient.focalY);
638 gradientEl.id = gradient.id;
639 me.getDefs().appendChild(gradientEl);
640 for (i = 0; i < ln; i++) {
641 stop = gradient.stops[i];
642 stopEl = me.createSvgElement("stop");
643 stopEl.setAttribute("offset", stop.offset + "%");
644 stopEl.setAttribute("stop-color", stop.color);
645 stopEl.setAttribute("stop-opacity",stop.opacity);
646 gradientEl.appendChild(stopEl);
649 gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
651 me.gradientsMap = gradientsMap;
654 <span id='Ext-draw-engine-Svg-method-hasCls'> /**
655 </span> * Checks if the specified CSS class exists on this element's DOM node.
656 * @param {String} className The CSS class to check for
657 * @return {Boolean} True if the class exists, else false
659 hasCls: function(sprite, className) {
660 return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
663 addCls: function(sprite, className) {
669 curCls = el.getAttribute('class') || '';
670 // Separate case is for speed
671 if (!Ext.isArray(className)) {
672 if (typeof className == 'string' && !this.hasCls(sprite, className)) {
673 el.set({ 'class': curCls + ' ' + className });
677 for (i = 0, len = className.length; i < len; i++) {
679 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
684 el.set({ 'class': ' ' + cls.join(' ') });
689 removeCls: function(sprite, className) {
692 curCls = el.getAttribute('class') || '',
693 i, idx, len, cls, elClasses;
694 if (!Ext.isArray(className)){
695 className = [className];
698 elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
699 for (i = 0, len = className.length; i < len; i++) {
701 if (typeof cls == 'string') {
702 cls = cls.replace(me.trimRe, '');
703 idx = Ext.Array.indexOf(elClasses, cls);
705 Ext.Array.erase(elClasses, idx, 1);
709 el.set({ 'class': elClasses.join(' ') });
713 destroy: function() {