ee333139d0bc16d599b6aeb1404e0fe6b91dc9f7
[groupbasedpolicy.git] / groupbasedpolicy-old-ui / module / src / main / resources / gbp / js / vectorizer.js
1 /*! JointJS v0.9.3 - JavaScript diagramming library  2015-05-22 
2
3
4 This Source Code Form is subject to the terms of the Mozilla Public
5 License, v. 2.0. If a copy of the MPL was not distributed with this
6 file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  */
8 // Vectorizer.
9 // -----------
10
11 // A tiny library for making your live easier when dealing with SVG.
12 // The only Vectorizer dependency is the Geometry library.
13
14 // Copyright © 2012 - 2015 client IO (http://client.io)
15
16 (function(root, factory) {
17
18     if (typeof define === 'function' && define.amd) {
19
20         // AMD. Register as an anonymous module.
21         define(['app/gbp/js/geometry'], function(g) {
22             return factory(g);
23         });
24         
25
26     } else if (typeof exports === 'object') {
27
28         // Node. Does not work with strict CommonJS, but
29         // only CommonJS-like environments that support module.exports,
30         // like Node.
31         module.exports = factory();
32
33     } else {
34
35         // Browser globals.
36         root.Vectorizer = root.V = factory();
37
38     }
39
40 }(this, function(g) {
41
42     var SVGsupported = typeof window === 'object' && !!(window.SVGAngle || document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1'));
43
44     // SVG support is required.
45     if (!SVGsupported) return function() {};
46
47     // XML namespaces.
48     var ns = {
49         xmlns: 'http://www.w3.org/2000/svg',
50         xlink: 'http://www.w3.org/1999/xlink'
51     };
52     // SVG version.
53     var SVGversion = '1.1';
54
55     // A function returning a unique identifier for this client session with every call.
56     var idCounter = 0;
57     function uniqueId() {
58         var id = ++idCounter + '';
59         return 'v-' + id;
60     }
61
62     // Create an SVG document element.
63     // If `content` is passed, it will be used as the SVG content of the `<svg>` root element.
64     function createSvgDocument(content) {
65
66         var svg = '<svg xmlns="' + ns.xmlns + '" xmlns:xlink="' + ns.xlink + '" version="' + SVGversion + '">' + (content || '') + '</svg>';
67         var parser = new DOMParser();
68         parser.async = false;
69         return parser.parseFromString(svg, 'text/xml').documentElement;
70     }
71
72     // Create SVG element.
73     // -------------------
74
75     function createElement(el, attrs, children) {
76
77         var i, len;
78
79         if (!el) return undefined;
80
81         // If `el` is an object, it is probably a native SVG element. Wrap it to VElement.
82         if (typeof el === 'object') {
83             return new VElement(el);
84         }
85         attrs = attrs || {};
86
87         // If `el` is a `'svg'` or `'SVG'` string, create a new SVG canvas.
88         if (el.toLowerCase() === 'svg') {
89
90             return new VElement(createSvgDocument());
91
92         } else if (el[0] === '<') {
93             // Create element from an SVG string.
94             // Allows constructs of type: `document.appendChild(Vectorizer('<rect></rect>').node)`.
95
96             var svgDoc = createSvgDocument(el);
97
98             // Note that `createElement()` might also return an array should the SVG string passed as
99             // the first argument contain more then one root element.
100             if (svgDoc.childNodes.length > 1) {
101
102                 // Map child nodes to `VElement`s.
103                 var ret = [];
104                 for (i = 0, len = svgDoc.childNodes.length; i < len; i++) {
105
106                     var childNode = svgDoc.childNodes[i];
107                     ret.push(new VElement(document.importNode(childNode, true)));
108                 }
109                 return ret;
110             }
111
112             return new VElement(document.importNode(svgDoc.firstChild, true));
113         }
114
115         el = document.createElementNS(ns.xmlns, el);
116
117         // Set attributes.
118         for (var key in attrs) {
119
120             setAttribute(el, key, attrs[key]);
121         }
122
123         // Normalize `children` array.
124         if (Object.prototype.toString.call(children) != '[object Array]') children = [children];
125
126         // Append children if they are specified.
127         for (i = 0, len = (children[0] && children.length) || 0; i < len; i++) {
128             var child = children[i];
129             el.appendChild(child instanceof VElement ? child.node : child);
130         }
131
132         return new VElement(el);
133     }
134
135     function setAttribute(el, name, value) {
136
137         if (name.indexOf(':') > -1) {
138             // Attribute names can be namespaced. E.g. `image` elements
139             // have a `xlink:href` attribute to set the source of the image.
140             var combinedKey = name.split(':');
141             el.setAttributeNS(ns[combinedKey[0]], combinedKey[1], value);
142
143         } else if (name === 'id') {
144             el.id = value;
145         } else {
146             el.setAttribute(name, value);
147         }
148     }
149
150     function parseTransformString(transform) {
151         var translate,
152             rotate,
153             scale;
154
155         if (transform) {
156
157             var separator = /[ ,]+/;
158
159             var translateMatch = transform.match(/translate\((.*)\)/);
160             if (translateMatch) {
161                 translate = translateMatch[1].split(separator);
162             }
163             var rotateMatch = transform.match(/rotate\((.*)\)/);
164             if (rotateMatch) {
165                 rotate = rotateMatch[1].split(separator);
166             }
167             var scaleMatch = transform.match(/scale\((.*)\)/);
168             if (scaleMatch) {
169                 scale = scaleMatch[1].split(separator);
170             }
171         }
172
173         var sx = (scale && scale[0]) ? parseFloat(scale[0]) : 1;
174
175         return {
176             translate: {
177                 tx: (translate && translate[0]) ? parseInt(translate[0], 10) : 0,
178                 ty: (translate && translate[1]) ? parseInt(translate[1], 10) : 0
179             },
180             rotate: {
181                 angle: (rotate && rotate[0]) ? parseInt(rotate[0], 10) : 0,
182                 cx: (rotate && rotate[1]) ? parseInt(rotate[1], 10) : undefined,
183                 cy: (rotate && rotate[2]) ? parseInt(rotate[2], 10) : undefined
184             },
185             scale: {
186                 sx: sx,
187                 sy: (scale && scale[1]) ? parseFloat(scale[1]) : sx
188             }
189         };
190     }
191
192
193     // Matrix decomposition.
194     // ---------------------
195
196     function deltaTransformPoint(matrix, point) {
197
198         var dx = point.x * matrix.a + point.y * matrix.c + 0;
199         var dy = point.x * matrix.b + point.y * matrix.d + 0;
200         return { x: dx, y: dy };
201     }
202
203     function decomposeMatrix(matrix) {
204
205         // @see https://gist.github.com/2052247
206
207         // calculate delta transform point
208         var px = deltaTransformPoint(matrix, { x: 0, y: 1 });
209         var py = deltaTransformPoint(matrix, { x: 1, y: 0 });
210
211         // calculate skew
212         var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
213         var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));
214
215         return {
216
217             translateX: matrix.e,
218             translateY: matrix.f,
219             scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
220             scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
221             skewX: skewX,
222             skewY: skewY,
223             rotation: skewX // rotation is the same as skew x
224         };
225     }
226
227     // VElement.
228     // ---------
229
230     function VElement(el) {
231         this.node = el;
232         if (!this.node.id) {
233             this.node.id = uniqueId();
234         }
235     }
236
237     // VElement public API.
238     // --------------------
239
240     VElement.prototype = {
241
242         translate: function(tx, ty, opt) {
243
244             opt = opt || {};
245             ty = ty || 0;
246
247             var transformAttr = this.attr('transform') || '';
248             var transform = parseTransformString(transformAttr);
249
250             // Is it a getter?
251             if (typeof tx === 'undefined') {
252                 return transform.translate;
253             }
254
255             transformAttr = transformAttr.replace(/translate\([^\)]*\)/g, '').trim();
256
257             var newTx = opt.absolute ? tx : transform.translate.tx + tx;
258             var newTy = opt.absolute ? ty : transform.translate.ty + ty;
259             var newTranslate = 'translate(' + newTx + ',' + newTy + ')';
260
261             // Note that `translate()` is always the first transformation. This is
262             // usually the desired case.
263             this.attr('transform', (newTranslate + ' ' + transformAttr).trim());
264             return this;
265         },
266
267         rotate: function(angle, cx, cy, opt) {
268
269             opt = opt || {};
270
271             var transformAttr = this.attr('transform') || '';
272             var transform = parseTransformString(transformAttr);
273
274             // Is it a getter?
275             if (typeof angle === 'undefined') {
276                 return transform.rotate;
277             }
278
279             transformAttr = transformAttr.replace(/rotate\([^\)]*\)/g, '').trim();
280
281             angle %= 360;
282
283             var newAngle = opt.absolute ? angle : transform.rotate.angle + angle;
284             var newOrigin = (cx !== undefined && cy !== undefined) ? ',' + cx + ',' + cy : '';
285             var newRotate = 'rotate(' + newAngle + newOrigin + ')';
286
287             this.attr('transform', (transformAttr + ' ' + newRotate).trim());
288             return this;
289         },
290
291         // Note that `scale` as the only transformation does not combine with previous values.
292         scale: function(sx, sy) {
293             sy = (typeof sy === 'undefined') ? sx : sy;
294
295             var transformAttr = this.attr('transform') || '';
296             var transform = parseTransformString(transformAttr);
297
298             // Is it a getter?
299             if (typeof sx === 'undefined') {
300                 return transform.scale;
301             }
302
303             transformAttr = transformAttr.replace(/scale\([^\)]*\)/g, '').trim();
304
305             var newScale = 'scale(' + sx + ',' + sy + ')';
306
307             this.attr('transform', (transformAttr + ' ' + newScale).trim());
308             return this;
309         },
310
311         // Get SVGRect that contains coordinates and dimension of the real bounding box,
312         // i.e. after transformations are applied.
313         // If `target` is specified, bounding box will be computed relatively to `target` element.
314         bbox: function(withoutTransformations, target) {
315
316             // If the element is not in the live DOM, it does not have a bounding box defined and
317             // so fall back to 'zero' dimension element.
318             if (!this.node.ownerSVGElement) return { x: 0, y: 0, width: 0, height: 0 };
319
320             var box;
321             try {
322
323                 box = this.node.getBBox();
324
325                 // Opera returns infinite values in some cases.
326                 // Note that Infinity | 0 produces 0 as opposed to Infinity || 0.
327                 // We also have to create new object as the standard says that you can't
328                 // modify the attributes of a bbox.
329                 box = { x: box.x | 0, y: box.y | 0, width: box.width | 0, height: box.height | 0 };
330
331             } catch (e) {
332
333                 // Fallback for IE.
334                 box = {
335                     x: this.node.clientLeft,
336                     y: this.node.clientTop,
337                     width: this.node.clientWidth,
338                     height: this.node.clientHeight
339                 };
340             }
341
342             if (withoutTransformations) {
343
344                 return box;
345             }
346
347             var matrix = this.node.getTransformToElement(target || this.node.ownerSVGElement);
348
349             return V.transformRect(box, matrix);
350         },
351
352         text: function(content, opt) {
353
354             opt = opt || {};
355             var lines = content.split('\n');
356             var i = 0;
357             var tspan;
358
359             // `alignment-baseline` does not work in Firefox.
360             // Setting `dominant-baseline` on the `<text>` element doesn't work in IE9.
361             // In order to have the 0,0 coordinate of the `<text>` element (or the first `<tspan>`)
362             // in the top left corner we translate the `<text>` element by `0.8em`.
363             // See `http://www.w3.org/Graphics/SVG/WG/wiki/How_to_determine_dominant_baseline`.
364             // See also `http://apike.ca/prog_svg_text_style.html`.
365             this.attr('y', '0.8em');
366
367             // An empty text gets rendered into the DOM in webkit-based browsers.
368             // In order to unify this behaviour across all browsers
369             // we rather hide the text element when it's empty.
370             this.attr('display', content ? null : 'none');
371
372             // Preserve spaces. In other words, we do not want consecutive spaces to get collapsed to one.
373             this.node.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
374
375             // Easy way to erase all `<tspan>` children;
376             this.node.textContent = '';
377
378             var textNode = this.node;
379
380             if (opt.textPath) {
381
382                 // Wrap the text in the SVG <textPath> element that points
383                 // to a path defined by `opt.textPath` inside the internal `<defs>` element.
384                 var defs = this.find('defs');
385                 if (defs.length === 0) {
386                     defs = createElement('defs');
387                     this.append(defs);
388                 }
389
390                 // If `opt.textPath` is a plain string, consider it to be directly the
391                 // SVG path data for the text to go along (this is a shortcut).
392                 // Otherwise if it is an object and contains the `d` property, then this is our path.
393                 var d = Object(opt.textPath) === opt.textPath ? opt.textPath.d : opt.textPath;
394                 if (d) {
395                     var path = createElement('path', { d: d });
396                     defs.append(path);
397                 }
398
399                 var textPath = createElement('textPath');
400                 // Set attributes on the `<textPath>`. The most important one
401                 // is the `xlink:href` that points to our newly created `<path/>` element in `<defs/>`.
402                 // Note that we also allow the following construct:
403                 // `t.text('my text', { textPath: { 'xlink:href': '#my-other-path' } })`.
404                 // In other words, one can completely skip the auto-creation of the path
405                 // and use any other arbitrary path that is in the document.
406                 if (!opt.textPath['xlink:href'] && path) {
407                     textPath.attr('xlink:href', '#' + path.node.id);
408                 }
409
410                 if (Object(opt.textPath) === opt.textPath) {
411                     textPath.attr(opt.textPath);
412                 }
413                 this.append(textPath);
414                 // Now all the `<tspan>`s will be inside the `<textPath>`.
415                 textNode = textPath.node;
416             }
417
418             if (lines.length === 1) {
419                 textNode.textContent = content;
420                 return this;
421             }
422
423             for (; i < lines.length; i++) {
424
425                 // Shift all the <tspan> but first by one line (`1em`)
426                 tspan = V('tspan', { dy: (i == 0 ? '0em' : opt.lineHeight || '1em'), x: this.attr('x') || 0 });
427                 tspan.addClass('v-line');
428                 if (!lines[i]) {
429                     tspan.addClass('v-empty-line');
430                 }
431                 // Make sure the textContent is never empty. If it is, add an additional
432                 // space (an invisible character) so that following lines are correctly
433                 // relatively positioned. `dy=1em` won't work with empty lines otherwise.
434                 tspan.node.textContent = lines[i] || ' ';
435
436                 V(textNode).append(tspan);
437             }
438             return this;
439         },
440
441         attr: function(name, value) {
442
443             if (typeof name === 'undefined') {
444                 // Return all attributes.
445                 var attributes = this.node.attributes;
446                 var attrs = {};
447                 for (var i = 0; i < attributes.length; i++) {
448                     attrs[attributes[i].nodeName] = attributes[i].nodeValue;
449                 }
450                 return attrs;
451             }
452
453             if (typeof name === 'string' && typeof value === 'undefined') {
454                 return this.node.getAttribute(name);
455             }
456
457             if (typeof name === 'object') {
458
459                 for (var attrName in name) {
460                     if (name.hasOwnProperty(attrName)) {
461                         setAttribute(this.node, attrName, name[attrName]);
462                     }
463                 }
464
465             } else {
466
467                 setAttribute(this.node, name, value);
468             }
469
470             return this;
471         },
472
473         remove: function() {
474             if (this.node.parentNode) {
475                 this.node.parentNode.removeChild(this.node);
476             }
477         },
478
479         append: function(el) {
480
481             var els = el;
482
483             if (Object.prototype.toString.call(el) !== '[object Array]') {
484
485                 els = [el];
486             }
487
488             for (var i = 0, len = els.length; i < len; i++) {
489                 el = els[i];
490                 this.node.appendChild(el instanceof VElement ? el.node : el);
491             }
492
493             return this;
494         },
495
496         prepend: function(el) {
497             this.node.insertBefore(el instanceof VElement ? el.node : el, this.node.firstChild);
498         },
499
500         svg: function() {
501
502             return this.node instanceof window.SVGSVGElement ? this : V(this.node.ownerSVGElement);
503         },
504
505         defs: function() {
506
507             var defs = this.svg().node.getElementsByTagName('defs');
508
509             return (defs && defs.length) ? V(defs[0]) : undefined;
510         },
511
512         clone: function() {
513             var clone = V(this.node.cloneNode(true));
514             // Note that clone inherits also ID. Therefore, we need to change it here.
515             clone.node.id = uniqueId();
516             return clone;
517         },
518
519         findOne: function(selector) {
520
521             var found = this.node.querySelector(selector);
522             return found ? V(found) : undefined;
523         },
524
525         find: function(selector) {
526
527             var nodes = this.node.querySelectorAll(selector);
528
529             // Map DOM elements to `VElement`s.
530             for (var i = 0, len = nodes.length; i < len; i++) {
531                 nodes[i] = V(nodes[i]);
532             }
533             return nodes;
534         },
535
536         // Find an index of an element inside its container.
537         index: function() {
538
539             var index = 0;
540             var node = this.node.previousSibling;
541
542             while (node) {
543                 // nodeType 1 for ELEMENT_NODE
544                 if (node.nodeType === 1) index++;
545                 node = node.previousSibling;
546             }
547
548             return index;
549         },
550
551         // Convert global point into the coordinate space of this element.
552         toLocalPoint: function(x, y) {
553
554             var svg = this.svg().node;
555
556             var p = svg.createSVGPoint();
557             p.x = x;
558             p.y = y;
559
560             try {
561
562                 var globalPoint = p.matrixTransform(svg.getScreenCTM().inverse());
563                 var globalToLocalMatrix = this.node.getTransformToElement(svg).inverse();
564
565             } catch (e) {
566                 // IE9 throws an exception in odd cases. (`Unexpected call to method or property access`)
567                 // We have to make do with the original coordianates.
568                 return p;
569             }
570
571             return globalPoint.matrixTransform(globalToLocalMatrix);
572         },
573
574         translateCenterToPoint: function(p) {
575
576             var bbox = this.bbox();
577             var center = g.rect(bbox).center();
578
579             this.translate(p.x - center.x, p.y - center.y);
580         },
581
582         // Efficiently auto-orient an element. This basically implements the orient=auto attribute
583         // of markers. The easiest way of understanding on what this does is to imagine the element is an
584         // arrowhead. Calling this method on the arrowhead makes it point to the `position` point while
585         // being auto-oriented (properly rotated) towards the `reference` point.
586         // `target` is the element relative to which the transformations are applied. Usually a viewport.
587         translateAndAutoOrient: function(position, reference, target) {
588
589             // Clean-up previously set transformations except the scale. If we didn't clean up the
590             // previous transformations then they'd add up with the old ones. Scale is an exception as
591             // it doesn't add up, consider: `this.scale(2).scale(2).scale(2)`. The result is that the
592             // element is scaled by the factor 2, not 8.
593
594             var s = this.scale();
595             this.attr('transform', '');
596             this.scale(s.sx, s.sy);
597
598             var svg = this.svg().node;
599             var bbox = this.bbox(false, target);
600
601             // 1. Translate to origin.
602             var translateToOrigin = svg.createSVGTransform();
603             translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);
604
605             // 2. Rotate around origin.
606             var rotateAroundOrigin = svg.createSVGTransform();
607             var angle = g.point(position).changeInAngle(position.x - reference.x, position.y - reference.y, reference);
608             rotateAroundOrigin.setRotate(angle, 0, 0);
609
610             // 3. Translate to the `position` + the offset (half my width) towards the `reference` point.
611             var translateFinal = svg.createSVGTransform();
612             var finalPosition = g.point(position).move(reference, bbox.width / 2);
613             translateFinal.setTranslate(position.x + (position.x - finalPosition.x), position.y + (position.y - finalPosition.y));
614
615             // 4. Apply transformations.
616             var ctm = this.node.getTransformToElement(target);
617             var transform = svg.createSVGTransform();
618             transform.setMatrix(
619                 translateFinal.matrix.multiply(
620                     rotateAroundOrigin.matrix.multiply(
621                         translateToOrigin.matrix.multiply(
622                             ctm)))
623             );
624
625             // Instead of directly setting the `matrix()` transform on the element, first, decompose
626             // the matrix into separate transforms. This allows us to use normal Vectorizer methods
627             // as they don't work on matrices. An example of this is to retrieve a scale of an element.
628             // this.node.transform.baseVal.initialize(transform);
629
630             var decomposition = decomposeMatrix(transform.matrix);
631
632             this.translate(decomposition.translateX, decomposition.translateY);
633             this.rotate(decomposition.rotation);
634             // Note that scale has been already applied, hence the following line stays commented. (it's here just for reference).
635             //this.scale(decomposition.scaleX, decomposition.scaleY);
636
637             return this;
638         },
639
640         animateAlongPath: function(attrs, path) {
641
642             var animateMotion = V('animateMotion', attrs);
643             var mpath = V('mpath', { 'xlink:href': '#' + V(path).node.id });
644
645             animateMotion.append(mpath);
646
647             this.append(animateMotion);
648             try {
649                 animateMotion.node.beginElement();
650             } catch (e) {
651                 // Fallback for IE 9.
652                 // Run the animation programatically if FakeSmile (`http://leunen.me/fakesmile/`) present
653                 if (document.documentElement.getAttribute('smiling') === 'fake') {
654
655                     // Register the animation. (See `https://answers.launchpad.net/smil/+question/203333`)
656                     var animation = animateMotion.node;
657                     animation.animators = [];
658
659                     var animationID = animation.getAttribute('id');
660                     if (animationID) id2anim[animationID] = animation;
661
662                     var targets = getTargets(animation);
663                     for (var i = 0, len = targets.length; i < len; i++) {
664                         var target = targets[i];
665                         var animator = new Animator(animation, target, i);
666                         animators.push(animator);
667                         animation.animators[i] = animator;
668                         animator.register();
669                     }
670                 }
671             }
672         },
673
674         hasClass: function(className) {
675
676             return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.node.getAttribute('class'));
677         },
678
679         addClass: function(className) {
680
681             if (!this.hasClass(className)) {
682                 var prevClasses = this.node.getAttribute('class') || '';
683                 this.node.setAttribute('class', (prevClasses + ' ' + className).trim());
684             }
685
686             return this;
687         },
688
689         removeClass: function(className) {
690
691             if (this.hasClass(className)) {
692                 var newClasses = this.node.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2');
693                 this.node.setAttribute('class', newClasses);
694             }
695
696             return this;
697         },
698
699         toggleClass: function(className, toAdd) {
700
701             var toRemove = typeof toAdd === 'undefined' ? this.hasClass(className) : !toAdd;
702
703             if (toRemove) {
704                 this.removeClass(className);
705             } else {
706                 this.addClass(className);
707             }
708
709             return this;
710         },
711
712         // Interpolate path by discrete points. The precision of the sampling
713         // is controlled by `interval`. In other words, `sample()` will generate
714         // a point on the path starting at the beginning of the path going to the end
715         // every `interval` pixels.
716         // The sampler can be very useful for e.g. finding intersection between two
717         // paths (finding the two closest points from two samples).
718         sample: function(interval) {
719
720             interval = interval || 1;
721             var node = this.node;
722             var length = node.getTotalLength();
723             var samples = [];
724             var distance = 0;
725             var sample;
726             while (distance < length) {
727                 sample = node.getPointAtLength(distance);
728                 samples.push({ x: sample.x, y: sample.y, distance: distance });
729                 distance += interval;
730             }
731             return samples;
732         },
733
734         convertToPath: function() {
735
736             var path = createElement('path');
737             path.attr(this.attr());
738             var d = this.convertToPathData();
739             if (d) {
740                 path.attr('d', d);
741             }
742             return path;
743         },
744
745         convertToPathData: function() {
746
747             var tagName = this.node.tagName.toUpperCase();
748
749             switch (tagName) {
750             case 'PATH':
751                 return this.attr('d');
752             case 'LINE':
753                 return convertLineToPathData(this.node);
754             case 'POLYGON':
755                 return convertPolygonToPathData(this.node);
756             case 'POLYLINE':
757                 return convertPolylineToPathData(this.node);
758             case 'ELLIPSE':
759                 return convertEllipseToPathData(this.node);
760             case 'CIRCLE':
761                 return convertCircleToPathData(this.node);
762             case 'RECT':
763                 return convertRectToPathData(this.node);
764             }
765
766             throw new Error(tagName + ' cannot be converted to PATH.');
767         },
768
769         // Find the intersection of a line starting in the center
770         // of the SVG `node` ending in the point `ref`.
771         // `target` is an SVG element to which `node`s transformations are relative to.
772         // In JointJS, `target` is the `paper.viewport` SVG group element.
773         // Note that `ref` point must be in the coordinate system of the `target` for this function to work properly.
774         // Returns a point in the `target` coordinte system (the same system as `ref` is in) if
775         // an intersection is found. Returns `undefined` otherwise.
776         findIntersection: function(ref, target) {
777
778             var svg = this.svg().node;
779             target = target || svg;
780             var bbox = g.rect(this.bbox(false, target));
781             var center = bbox.center();
782             var spot = bbox.intersectionWithLineFromCenterToPoint(ref);
783
784             if (!spot) return undefined;
785
786             var tagName = this.node.localName.toUpperCase();
787
788             // Little speed up optimalization for `<rect>` element. We do not do conversion
789             // to path element and sampling but directly calculate the intersection through
790             // a transformed geometrical rectangle.
791             if (tagName === 'RECT') {
792
793                 var gRect = g.rect(
794                     parseFloat(this.attr('x') || 0),
795                     parseFloat(this.attr('y') || 0),
796                     parseFloat(this.attr('width')),
797                     parseFloat(this.attr('height'))
798                 );
799                 // Get the rect transformation matrix with regards to the SVG document.
800                 var rectMatrix = this.node.getTransformToElement(target);
801                 // Decompose the matrix to find the rotation angle.
802                 var rectMatrixComponents = V.decomposeMatrix(rectMatrix);
803                 // Now we want to rotate the rectangle back so that we
804                 // can use `intersectionWithLineFromCenterToPoint()` passing the angle as the second argument.
805                 var resetRotation = svg.createSVGTransform();
806                 resetRotation.setRotate(-rectMatrixComponents.rotation, center.x, center.y);
807                 var rect = V.transformRect(gRect, resetRotation.matrix.multiply(rectMatrix));
808                 spot = g.rect(rect).intersectionWithLineFromCenterToPoint(ref, rectMatrixComponents.rotation);
809
810             } else if (tagName === 'PATH' || tagName === 'POLYGON' || tagName === 'POLYLINE' || tagName === 'CIRCLE' || tagName === 'ELLIPSE') {
811
812                 var pathNode = (tagName === 'PATH') ? this : this.convertToPath();
813                 var samples = pathNode.sample();
814                 var minDistance = Infinity;
815                 var closestSamples = [];
816
817                 for (var i = 0, len = samples.length; i < len; i++) {
818
819                     var sample = samples[i];
820                     // Convert the sample point in the local coordinate system to the global coordinate system.
821                     var gp = V.createSVGPoint(sample.x, sample.y);
822                     gp = gp.matrixTransform(this.node.getTransformToElement(target));
823                     sample = g.point(gp);
824                     var centerDistance = sample.distance(center);
825                     // Penalize a higher distance to the reference point by 10%.
826                     // This gives better results. This is due to
827                     // inaccuracies introduced by rounding errors and getPointAtLength() returns.
828                     var refDistance = sample.distance(ref) * 1.1;
829                     var distance = centerDistance + refDistance;
830                     if (distance < minDistance) {
831                         minDistance = distance;
832                         closestSamples = [{ sample: sample, refDistance: refDistance }];
833                     } else if (distance < minDistance + 1) {
834                         closestSamples.push({ sample: sample, refDistance: refDistance });
835                     }
836                 }
837                 closestSamples.sort(function(a, b) { return a.refDistance - b.refDistance; });
838                 spot = closestSamples[0].sample;
839             }
840
841             return spot;
842         }
843     };
844
845     function convertLineToPathData(line) {
846
847         line = createElement(line);
848         var d = [
849             'M', line.attr('x1'), line.attr('y1'),
850             'L', line.attr('x2'), line.attr('y2')
851         ].join(' ');
852         return d;
853     }
854
855     function convertPolygonToPathData(polygon) {
856
857         polygon = createElement(polygon);
858         var points = polygon.node.points;
859
860         var d = [];
861         var p;
862         for (var i = 0; i < points.length; i++) {
863             p = points[i];
864             d.push(i === 0 ? 'M' : 'L', p.x, p.y);
865         }
866         d.push('Z');
867         return d.join(' ');
868     }
869
870     function convertPolylineToPathData(polyline) {
871
872         polyline = createElement(polyline);
873         var points = polyline.node.points;
874
875         var d = [];
876         var p;
877         for (var i = 0; i < points.length; i++) {
878             p = points[i];
879             d.push(i === 0 ? 'M' : 'L', p.x, p.y);
880         }
881         return d.join(' ');
882     }
883
884     var KAPPA = 0.5522847498307935;
885
886     function convertCircleToPathData(circle) {
887
888         circle = createElement(circle);
889         var cx = parseFloat(circle.attr('cx')) || 0;
890         var cy = parseFloat(circle.attr('cy')) || 0;
891         var r = parseFloat(circle.attr('r'));
892         var cd = r * KAPPA; // Control distance.
893
894         var d = [
895             'M', cx, cy - r,    // Move to the first point.
896             'C', cx + cd, cy - r, cx + r, cy - cd, cx + r, cy, // I. Quadrant.
897             'C', cx + r, cy + cd, cx + cd, cy + r, cx, cy + r, // II. Quadrant.
898             'C', cx - cd, cy + r, cx - r, cy + cd, cx - r, cy, // III. Quadrant.
899             'C', cx - r, cy - cd, cx - cd, cy - r, cx, cy - r, // IV. Quadrant.
900             'Z'
901         ].join(' ');
902         return d;
903     }
904
905     function convertEllipseToPathData(ellipse) {
906
907         ellipse = createElement(ellipse);
908         var cx = parseFloat(ellipse.attr('cx')) || 0;
909         var cy = parseFloat(ellipse.attr('cy')) || 0;
910         var rx = parseFloat(ellipse.attr('rx'));
911         var ry = parseFloat(ellipse.attr('ry')) || rx;
912         var cdx = rx * KAPPA; // Control distance x.
913         var cdy = ry * KAPPA; // Control distance y.
914
915         var d = [
916             'M', cx, cy - ry,    // Move to the first point.
917             'C', cx + cdx, cy - ry, cx + rx, cy - cdy, cx + rx, cy, // I. Quadrant.
918             'C', cx + rx, cy + cdy, cx + cdx, cy + ry, cx, cy + ry, // II. Quadrant.
919             'C', cx - cdx, cy + ry, cx - rx, cy + cdy, cx - rx, cy, // III. Quadrant.
920             'C', cx - rx, cy - cdy, cx - cdx, cy - ry, cx, cy - ry, // IV. Quadrant.
921             'Z'
922         ].join(' ');
923         return d;
924     }
925
926     function convertRectToPathData(rect) {
927
928         rect = createElement(rect);
929         var x = parseFloat(rect.attr('x')) || 0;
930         var y = parseFloat(rect.attr('y')) || 0;
931         var width = parseFloat(rect.attr('width')) || 0;
932         var height = parseFloat(rect.attr('height')) || 0;
933         var rx = parseFloat(rect.attr('rx')) || 0;
934         var ry = parseFloat(rect.attr('ry')) || 0;
935         var bbox = g.rect(x, y, width, height);
936
937         var d;
938
939         if (!rx && !ry) {
940
941             d = [
942                 'M', bbox.origin().x, bbox.origin().y,
943                 'H', bbox.corner().x,
944                 'V', bbox.corner().y,
945                 'H', bbox.origin().x,
946                 'V', bbox.origin().y,
947                 'Z'
948             ].join(' ');
949
950         } else {
951
952             var r = x + width;
953             var b = y + height;
954             d = [
955                 'M', x + rx, y,
956                 'L', r - rx, y,
957                 'Q', r, y, r, y + ry,
958                 'L', r, y + height - ry,
959                 'Q', r, b, r - rx, b,
960                 'L', x + rx, b,
961                 'Q', x, b, x, b - rx,
962                 'L', x, y + ry,
963                 'Q', x, y, x + rx, y,
964                 'Z'
965             ].join(' ');
966         }
967         return d;
968     }
969
970     // Convert a rectangle to SVG path commands. `r` is an object of the form:
971     // `{ x: [number], y: [number], width: [number], height: [number], top-ry: [number], top-ry: [number], bottom-rx: [number], bottom-ry: [number] }`,
972     // where `x, y, width, height` are the usual rectangle attributes and [top-/bottom-]rx/ry allows for
973     // specifying radius of the rectangle for all its sides (as opposed to the built-in SVG rectangle
974     // that has only `rx` and `ry` attributes).
975     function rectToPath(r) {
976
977         var topRx = r.rx || r['top-rx'] || 0;
978         var bottomRx = r.rx || r['bottom-rx'] || 0;
979         var topRy = r.ry || r['top-ry'] || 0;
980         var bottomRy = r.ry || r['bottom-ry'] || 0;
981
982         return [
983             'M', r.x, r.y + topRy,
984             'v', r.height - topRy - bottomRy,
985             'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy,
986             'h', r.width - 2 * bottomRx,
987             'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy,
988             'v', -(r.height - bottomRy - topRy),
989             'a', topRx, topRy, 0, 0, 0, -topRx, -topRy,
990             'h', -(r.width - 2 * topRx),
991             'a', topRx, topRy, 0, 0, 0, -topRx, topRy
992         ].join(' ');
993     }
994
995     var V = createElement;
996
997     V.decomposeMatrix = decomposeMatrix;
998     V.rectToPath = rectToPath;
999
1000     var svgDocument = V('svg').node;
1001
1002     V.createSVGMatrix = function(m) {
1003
1004         var svgMatrix = svgDocument.createSVGMatrix();
1005         for (var component in m) {
1006             svgMatrix[component] = m[component];
1007         }
1008
1009         return svgMatrix;
1010     };
1011
1012     V.createSVGTransform = function() {
1013
1014         return svgDocument.createSVGTransform();
1015     };
1016
1017     V.createSVGPoint = function(x, y) {
1018
1019         var p = svgDocument.createSVGPoint();
1020         p.x = x;
1021         p.y = y;
1022         return p;
1023     };
1024
1025     V.transformRect = function(r, matrix) {
1026
1027         var p = svgDocument.createSVGPoint();
1028
1029         p.x = r.x;
1030         p.y = r.y;
1031         var corner1 = p.matrixTransform(matrix);
1032
1033         p.x = r.x + r.width;
1034         p.y = r.y;
1035         var corner2 = p.matrixTransform(matrix);
1036
1037         p.x = r.x + r.width;
1038         p.y = r.y + r.height;
1039         var corner3 = p.matrixTransform(matrix);
1040
1041         p.x = r.x;
1042         p.y = r.y + r.height;
1043         var corner4 = p.matrixTransform(matrix);
1044
1045         var minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x);
1046         var maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x);
1047         var minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y);
1048         var maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y);
1049
1050         return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
1051     };
1052
1053     return V;
1054
1055 }));