text: /*! JointJS v0.9.7 - JavaScript diagramming library  2016-01-28 
  
  
  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.
   */
  (function(root, factory) {
  
      if (typeof define === 'function' && define.amd) {
  
          // AMD. Register as an anonymous module.
          define(['g'], function(g) {
              return factory(g);
          });
  
      } else if (typeof exports === 'object') {
  
          // Node. Does not work with strict CommonJS, but
          // only CommonJS-like environments that support module.exports,
          // like Node.
          var g = require('./geometry');
  
          module.exports = factory(g);
  
      } else {
  
          // Browser globals.
          var g = root.g;
  
          root.Vectorizer = root.V = factory(g);
      }
  
  }(this, function(g) {
  
  // Vectorizer.
  // -----------
  
  // A tiny library for making your live easier when dealing with SVG.
  // The only Vectorizer dependency is the Geometry library.
  
  // Copyright © 2012 - 2015 client IO (http://client.io)
  
  var V;
  var Vectorizer;
  
  V = Vectorizer = (function() {
  
      var SVGsupported = typeof window === 'object' && !!(window.SVGAngle || document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1'));
  
      // SVG support is required.
      if (!SVGsupported) return function() {};
  
      // XML namespaces.
      var ns = {
          xmlns: 'http://www.w3.org/2000/svg',
          xlink: 'http://www.w3.org/1999/xlink'
      };
      // SVG version.
      var SVGversion = '1.1';
  
      // A function returning a unique identifier for this client session with every call.
      var idCounter = 0;
      function uniqueId() {
          var id = ++idCounter + '';
          return 'v-' + id;
      }
  
      // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm).
      // IE would otherwise collapse all spaces into one. This is used in the text() method but it is
      // also exposed so that the programmer can use it in case he needs to. This is useful e.g. in tests
      // when you want to compare the actual DOM text content without having to add the unicode character in
      // the place of all spaces.
      function sanitizeText(text) {
          return (text || '').replace(/ /g, '\u00A0');
      }
  
      function isObject(o) {
          return o === Object(o);
      }
  
      function isArray(o) {
          return Object.prototype.toString.call(o) == '[object Array]';
      }
  
      // Create an SVG document element.
      // If `content` is passed, it will be used as the SVG content of the `<svg>` root element.
      function createSvgDocument(content) {
  
          var svg = '<svg xmlns="' + ns.xmlns + '" xmlns:xlink="' + ns.xlink + '" version="' + SVGversion + '">' + (content || '') + '</svg>';
          var xml = parseXML(svg, { async: false });
          return xml.documentElement;
      }
  
      function parseXML(data, opt) {
  
          opt = opt || {};
  
          var xml;
  
          try {
              var parser = new DOMParser();
  
              if (typeof opt.async !== 'undefined') {
                  parser.async = opt.async;
              }
  
              xml = parser.parseFromString(data, 'text/xml');
          } catch (error) {
              xml = undefined;
          }
  
          if (!xml || xml.getElementsByTagName('parsererror').length) {
              throw new Error('Invalid XML: ' + data);
          }
  
          return xml;
      }
  
      // Create SVG element.
      // -------------------
  
      function createElement(el, attrs, children) {
  
          var i, len;
  
          if (!el) return undefined;
  
          // If `el` is an object, it is probably a native SVG element. Wrap it to VElement.
          if (typeof el === 'object') {
              return new VElement(el);
          }
          attrs = attrs || {};
  
          // If `el` is a `'svg'` or `'SVG'` string, create a new SVG canvas.
          if (el.toLowerCase() === 'svg') {
  
              return new VElement(createSvgDocument());
  
          } else if (el[0] === '<') {
              // Create element from an SVG string.
              // Allows constructs of type: `document.appendChild(Vectorizer('<rect></rect>').node)`.
  
              var svgDoc = createSvgDocument(el);
  
              // Note that `createElement()` might also return an array should the SVG string passed as
              // the first argument contain more then one root element.
              if (svgDoc.childNodes.length > 1) {
  
                  // Map child nodes to `VElement`s.
                  var ret = [];
                  for (i = 0, len = svgDoc.childNodes.length; i < len; i++) {
  
                      var childNode = svgDoc.childNodes[i];
                      ret.push(new VElement(document.importNode(childNode, true)));
                  }
                  return ret;
              }
  
              return new VElement(document.importNode(svgDoc.firstChild, true));
          }
  
          el = document.createElementNS(ns.xmlns, el);
  
          // Set attributes.
          for (var key in attrs) {
  
              setAttribute(el, key, attrs[key]);
          }
  
          // Normalize `children` array.
          if (Object.prototype.toString.call(children) != '[object Array]') children = [children];
  
          // Append children if they are specified.
          for (i = 0, len = (children[0] && children.length) || 0; i < len; i++) {
              var child = children[i];
              el.appendChild(child instanceof VElement ? child.node : child);
          }
  
          return new VElement(el);
      }
  
      function setAttribute(el, name, value) {
  
          if (name.indexOf(':') > -1) {
              // Attribute names can be namespaced. E.g. `image` elements
              // have a `xlink:href` attribute to set the source of the image.
              var combinedKey = name.split(':');
              el.setAttributeNS(ns[combinedKey[0]], combinedKey[1], value);
  
          } else if (name === 'id') {
              el.id = value;
          } else {
              el.setAttribute(name, value);
          }
      }
  
      function parseTransformString(transform) {
          var translate,
              rotate,
              scale;
  
          if (transform) {
  
              var separator = /[ ,]+/;
  
              var translateMatch = transform.match(/translate\((.*)\)/);
              if (translateMatch) {
                  translate = translateMatch[1].split(separator);
              }
              var rotateMatch = transform.match(/rotate\((.*)\)/);
              if (rotateMatch) {
                  rotate = rotateMatch[1].split(separator);
              }
              var scaleMatch = transform.match(/scale\((.*)\)/);
              if (scaleMatch) {
                  scale = scaleMatch[1].split(separator);
              }
          }
  
          var sx = (scale && scale[0]) ? parseFloat(scale[0]) : 1;
  
          return {
              translate: {
                  tx: (translate && translate[0]) ? parseInt(translate[0], 10) : 0,
                  ty: (translate && translate[1]) ? parseInt(translate[1], 10) : 0
              },
              rotate: {
                  angle: (rotate && rotate[0]) ? parseInt(rotate[0], 10) : 0,
                  cx: (rotate && rotate[1]) ? parseInt(rotate[1], 10) : undefined,
                  cy: (rotate && rotate[2]) ? parseInt(rotate[2], 10) : undefined
              },
              scale: {
                  sx: sx,
                  sy: (scale && scale[1]) ? parseFloat(scale[1]) : sx
              }
          };
      }
  
  
      // Matrix decomposition.
      // ---------------------
  
      function deltaTransformPoint(matrix, point) {
  
          var dx = point.x * matrix.a + point.y * matrix.c + 0;
          var dy = point.x * matrix.b + point.y * matrix.d + 0;
          return { x: dx, y: dy };
      }
  
      function decomposeMatrix(matrix) {
  
          // @see https://gist.github.com/2052247
  
          // calculate delta transform point
          var px = deltaTransformPoint(matrix, { x: 0, y: 1 });
          var py = deltaTransformPoint(matrix, { x: 1, y: 0 });
  
          // calculate skew
          var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90);
          var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x));
  
          return {
  
              translateX: matrix.e,
              translateY: matrix.f,
              scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
              scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
              skewX: skewX,
              skewY: skewY,
              rotation: skewX // rotation is the same as skew x
          };
      }
  
      // VElement.
      // ---------
  
      function VElement(el) {
          if (el instanceof VElement) {
              el = el.node;
          }
          this.node = el;
          if (!this.node.id) {
              this.node.id = uniqueId();
          }
      }
  
      // VElement public API.
      // --------------------
  
      VElement.prototype = {
  
          /**
           * @param {SVGGElement} toElem
           * @returns {SVGMatrix}
           */
          getTransformToElement: function(toElem) {
              return toElem.getScreenCTM().inverse().multiply(this.node.getScreenCTM());
          },
  
          translate: function(tx, ty, opt) {
  
              opt = opt || {};
              ty = ty || 0;
  
              var transformAttr = this.attr('transform') || '';
              var transform = parseTransformString(transformAttr);
  
              // Is it a getter?
              if (typeof tx === 'undefined') {
                  return transform.translate;
              }
  
              transformAttr = transformAttr.replace(/translate\([^\)]*\)/g, '').trim();
  
              var newTx = opt.absolute ? tx : transform.translate.tx + tx;
              var newTy = opt.absolute ? ty : transform.translate.ty + ty;
              var newTranslate = 'translate(' + newTx + ',' + newTy + ')';
  
              // Note that `translate()` is always the first transformation. This is
              // usually the desired case.
              this.attr('transform', (newTranslate + ' ' + transformAttr).trim());
              return this;
          },
  
          rotate: function(angle, cx, cy, opt) {
  
              opt = opt || {};
  
              var transformAttr = this.attr('transform') || '';
              var transform = parseTransformString(transformAttr);
  
              // Is it a getter?
              if (typeof angle === 'undefined') {
                  return transform.rotate;
              }
  
              transformAttr = transformAttr.replace(/rotate\([^\)]*\)/g, '').trim();
  
              angle %= 360;
  
              var newAngle = opt.absolute ? angle : transform.rotate.angle + angle;
              var newOrigin = (cx !== undefined && cy !== undefined) ? ',' + cx + ',' + cy : '';
              var newRotate = 'rotate(' + newAngle + newOrigin + ')';
  
              this.attr('transform', (transformAttr + ' ' + newRotate).trim());
              return this;
          },
  
          // Note that `scale` as the only transformation does not combine with previous values.
          scale: function(sx, sy) {
              sy = (typeof sy === 'undefined') ? sx : sy;
  
              var transformAttr = this.attr('transform') || '';
              var transform = parseTransformString(transformAttr);
  
              // Is it a getter?
              if (typeof sx === 'undefined') {
                  return transform.scale;
              }
  
              transformAttr = transformAttr.replace(/scale\([^\)]*\)/g, '').trim();
  
              var newScale = 'scale(' + sx + ',' + sy + ')';
  
              this.attr('transform', (transformAttr + ' ' + newScale).trim());
              return this;
          },
  
          // Get SVGRect that contains coordinates and dimension of the real bounding box,
          // i.e. after transformations are applied.
          // If `target` is specified, bounding box will be computed relatively to `target` element.
          bbox: function(withoutTransformations, target) {
  
              // If the element is not in the live DOM, it does not have a bounding box defined and
              // so fall back to 'zero' dimension element.
              if (!this.node.ownerSVGElement) return { x: 0, y: 0, width: 0, height: 0 };
  
              var box;
              try {
  
                  box = this.node.getBBox();
                  // We are creating a new object as the standard says that you can't
                  // modify the attributes of a bbox.
                  box = { x: box.x, y: box.y, width: box.width, height: box.height };
  
              } catch (e) {
  
                  // Fallback for IE.
                  box = {
                      x: this.node.clientLeft,
                      y: this.node.clientTop,
                      width: this.node.clientWidth,
                      height: this.node.clientHeight
                  };
              }
  
              if (withoutTransformations) {
  
                  return box;
              }
  
              var matrix = this.getTransformToElement(target || this.node.ownerSVGElement);
  
              return V.transformRect(box, matrix);
          },
  
          text: function(content, opt) {
  
              // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm).
              // IE would otherwise collapse all spaces into one.
              content = sanitizeText(content);
              opt = opt || {};
              var lines = content.split('\n');
              var i = 0;
              var tspan;
  
              // `alignment-baseline` does not work in Firefox.
              // Setting `dominant-baseline` on the `<text>` element doesn't work in IE9.
              // In order to have the 0,0 coordinate of the `<text>` element (or the first `<tspan>`)
              // in the top left corner we translate the `<text>` element by `0.8em`.
              // See `http://www.w3.org/Graphics/SVG/WG/wiki/How_to_determine_dominant_baseline`.
              // See also `http://apike.ca/prog_svg_text_style.html`.
              var y = this.attr('y');
              if (!y) {
                  this.attr('y', '0.8em');
              }
  
              // An empty text gets rendered into the DOM in webkit-based browsers.
              // In order to unify this behaviour across all browsers
              // we rather hide the text element when it's empty.
              this.attr('display', content ? null : 'none');
  
              // Preserve spaces. In other words, we do not want consecutive spaces to get collapsed to one.
              this.node.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
  
              // Easy way to erase all `<tspan>` children;
              this.node.textContent = '';
  
              var textNode = this.node;
  
              if (opt.textPath) {
  
                  // Wrap the text in the SVG <textPath> element that points
                  // to a path defined by `opt.textPath` inside the internal `<defs>` element.
                  var defs = this.find('defs');
                  if (defs.length === 0) {
                      defs = createElement('defs');
                      this.append(defs);
                  }
  
                  // If `opt.textPath` is a plain string, consider it to be directly the
                  // SVG path data for the text to go along (this is a shortcut).
                  // Otherwise if it is an object and contains the `d` property, then this is our path.
                  var d = Object(opt.textPath) === opt.textPath ? opt.textPath.d : opt.textPath;
                  if (d) {
                      var path = createElement('path', { d: d });
                      defs.append(path);
                  }
  
                  var textPath = createElement('textPath');
                  // Set attributes on the `<textPath>`. The most important one
                  // is the `xlink:href` that points to our newly created `<path/>` element in `<defs/>`.
                  // Note that we also allow the following construct:
                  // `t.text('my text', { textPath: { 'xlink:href': '#my-other-path' } })`.
                  // In other words, one can completely skip the auto-creation of the path
                  // and use any other arbitrary path that is in the document.
                  if (!opt.textPath['xlink:href'] && path) {
                      textPath.attr('xlink:href', '#' + path.node.id);
                  }
  
                  if (Object(opt.textPath) === opt.textPath) {
                      textPath.attr(opt.textPath);
                  }
                  this.append(textPath);
                  // Now all the `<tspan>`s will be inside the `<textPath>`.
                  textNode = textPath.node;
              }
  
              var offset = 0;
  
              for (var i = 0; i < lines.length; i++) {
  
                  var line = lines[i];
                  // Shift all the <tspan> but first by one line (`1em`)
                  var lineHeight = opt.lineHeight || '1em';
                  if (opt.lineHeight === 'auto') {
                      lineHeight = '1.5em';
                  }
                  var vLine = V('tspan', { dy: (i == 0 ? '0em' : lineHeight), x: this.attr('x') || 0 });
                  vLine.addClass('v-line');
  
                  if (line) {
  
                      if (opt.annotations) {
  
                          // Get the line height based on the biggest font size in the annotations for this line.
                          var maxFontSize = 0;
  
                          // Find the *compacted* annotations for this line.
                          var lineAnnotations = V.annotateString(lines[i], isArray(opt.annotations) ? opt.annotations : [opt.annotations], { offset: -offset, includeAnnotationIndices: opt.includeAnnotationIndices });
                          for (var j = 0; j < lineAnnotations.length; j++) {
  
                              var annotation = lineAnnotations[j];
                              if (isObject(annotation)) {
  
                                  var fontSize = parseInt(annotation.attrs['font-size'], 10);
                                  if (fontSize && fontSize > maxFontSize) {
                                      maxFontSize = fontSize;
                                  }
  
                                  tspan = V('tspan', annotation.attrs);
                                  if (opt.includeAnnotationIndices) {
                                      // If `opt.includeAnnotationIndices` is `true`,
                                      // set the list of indices of all the applied annotations
                                      // in the `annotations` attribute. This list is a comma
                                      // separated list of indices.
                                      tspan.attr('annotations', annotation.annotations);
                                  }
                                  if (annotation.attrs['class']) {
                                      tspan.addClass(annotation.attrs['class']);
                                  }
                                  tspan.node.textContent = annotation.t;
  
                              } else {
  
                                  tspan = document.createTextNode(annotation || ' ');
  
                              }
                              vLine.append(tspan);
                          }
  
                          if (opt.lineHeight === 'auto' && maxFontSize && i !== 0) {
  
                              vLine.attr('dy', (maxFontSize * 1.2) + 'px');
                          }
  
                      } else {
  
                          vLine.node.textContent = line;
                      }
  
                  } else {
  
                      // Make sure the textContent is never empty. If it is, add a dummy
                      // character and make it invisible, making the following lines correctly
                      // relatively positioned. `dy=1em` won't work with empty lines otherwise.
                      vLine.addClass('v-empty-line');
                      vLine.node.style.opacity = 0;
                      vLine.node.textContent = '-';
                  }
  
                  V(textNode).append(vLine);
  
                  offset += line.length + 1;      // + 1 = newline character.
              }
  
              return this;
          },
  
          attr: function(name, value) {
  
              if (typeof name === 'undefined') {
                  // Return all attributes.
                  var attributes = this.node.attributes;
                  var attrs = {};
                  for (var i = 0; i < attributes.length; i++) {
                      attrs[attributes[i].nodeName] = attributes[i].nodeValue;
                  }
                  return attrs;
              }
  
              if (typeof name === 'string' && typeof value === 'undefined') {
                  return this.node.getAttribute(name);
              }
  
              if (typeof name === 'object') {
  
                  for (var attrName in name) {
                      if (name.hasOwnProperty(attrName)) {
                          setAttribute(this.node, attrName, name[attrName]);
                      }
                  }
  
              } else {
  
                  setAttribute(this.node, name, value);
              }
  
              return this;
          },
  
          remove: function() {
              if (this.node.parentNode) {
                  this.node.parentNode.removeChild(this.node);
              }
          },
  
          append: function(el) {
  
              var els = el;
  
              if (Object.prototype.toString.call(el) !== '[object Array]') {
  
                  els = [el];
              }
  
              for (var i = 0, len = els.length; i < len; i++) {
                  el = els[i];
                  this.node.appendChild(el instanceof VElement ? el.node : el);
              }
  
              return this;
          },
  
          prepend: function(el) {
              this.node.insertBefore(el instanceof VElement ? el.node : el, this.node.firstChild);
          },
  
          svg: function() {
  
              return this.node instanceof window.SVGSVGElement ? this : V(this.node.ownerSVGElement);
          },
  
          defs: function() {
  
              var defs = this.svg().node.getElementsByTagName('defs');
  
              return (defs && defs.length) ? V(defs[0]) : undefined;
          },
  
          clone: function() {
              var clone = V(this.node.cloneNode(true));
              // Note that clone inherits also ID. Therefore, we need to change it here.
              clone.node.id = uniqueId();
              return clone;
          },
  
          findOne: function(selector) {
  
              var found = this.node.querySelector(selector);
              return found ? V(found) : undefined;
          },
  
          find: function(selector) {
  
              var nodes = this.node.querySelectorAll(selector);
  
              // Map DOM elements to `VElement`s.
              return Array.prototype.map.call(nodes, V);
          },
  
          // Find an index of an element inside its container.
          index: function() {
  
              var index = 0;
              var node = this.node.previousSibling;
  
              while (node) {
                  // nodeType 1 for ELEMENT_NODE
                  if (node.nodeType === 1) index++;
                  node = node.previousSibling;
              }
  
              return index;
          },
  
          findParentByClass: function(className, terminator) {
  
              var ownerSVGElement = this.node.ownerSVGElement;
              var node = this.node.parentNode;
  
              while (node && node !== terminator && node !== ownerSVGElement) {
  
                  var vel = V(node);
                  if (vel.hasClass(className)) {
                      return vel;
                  }
  
                  node = node.parentNode;
              }
  
              return null;
          },
  
          // Convert global point into the coordinate space of this element.
          toLocalPoint: function(x, y) {
  
              var svg = this.svg().node;
  
              var p = svg.createSVGPoint();
              p.x = x;
              p.y = y;
  
              try {
  
                  var globalPoint = p.matrixTransform(svg.getScreenCTM().inverse());
                  var globalToLocalMatrix = this.getTransformToElement(svg).inverse();
  
              } catch (e) {
                  // IE9 throws an exception in odd cases. (`Unexpected call to method or property access`)
                  // We have to make do with the original coordianates.
                  return p;
              }
  
              return globalPoint.matrixTransform(globalToLocalMatrix);
          },
  
          translateCenterToPoint: function(p) {
  
              var bbox = this.bbox();
              var center = g.rect(bbox).center();
  
              this.translate(p.x - center.x, p.y - center.y);
          },
  
          // Efficiently auto-orient an element. This basically implements the orient=auto attribute
          // of markers. The easiest way of understanding on what this does is to imagine the element is an
          // arrowhead. Calling this method on the arrowhead makes it point to the `position` point while
          // being auto-oriented (properly rotated) towards the `reference` point.
          // `target` is the element relative to which the transformations are applied. Usually a viewport.
          translateAndAutoOrient: function(position, reference, target) {
  
              // Clean-up previously set transformations except the scale. If we didn't clean up the
              // previous transformations then they'd add up with the old ones. Scale is an exception as
              // it doesn't add up, consider: `this.scale(2).scale(2).scale(2)`. The result is that the
              // element is scaled by the factor 2, not 8.
  
              var s = this.scale();
              this.attr('transform', '');
              this.scale(s.sx, s.sy);
  
              var svg = this.svg().node;
              var bbox = this.bbox(false, target);
  
              // 1. Translate to origin.
              var translateToOrigin = svg.createSVGTransform();
              translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);
  
              // 2. Rotate around origin.
              var rotateAroundOrigin = svg.createSVGTransform();
              var angle = g.point(position).changeInAngle(position.x - reference.x, position.y - reference.y, reference);
              rotateAroundOrigin.setRotate(angle, 0, 0);
  
              // 3. Translate to the `position` + the offset (half my width) towards the `reference` point.
              var translateFinal = svg.createSVGTransform();
              var finalPosition = g.point(position).move(reference, bbox.width / 2);
              translateFinal.setTranslate(position.x + (position.x - finalPosition.x), position.y + (position.y - finalPosition.y));
  
              // 4. Apply transformations.
              var ctm = this.getTransformToElement(target);
              var transform = svg.createSVGTransform();
              transform.setMatrix(
                  translateFinal.matrix.multiply(
                      rotateAroundOrigin.matrix.multiply(
                          translateToOrigin.matrix.multiply(
                              ctm)))
              );
  
              // Instead of directly setting the `matrix()` transform on the element, first, decompose
              // the matrix into separate transforms. This allows us to use normal Vectorizer methods
              // as they don't work on matrices. An example of this is to retrieve a scale of an element.
              // this.node.transform.baseVal.initialize(transform);
  
              var decomposition = decomposeMatrix(transform.matrix);
  
              this.translate(decomposition.translateX, decomposition.translateY);
              this.rotate(decomposition.rotation);
              // Note that scale has been already applied, hence the following line stays commented. (it's here just for reference).
              //this.scale(decomposition.scaleX, decomposition.scaleY);
  
              return this;
          },
  
          animateAlongPath: function(attrs, path) {
  
              var animateMotion = V('animateMotion', attrs);
              var mpath = V('mpath', { 'xlink:href': '#' + V(path).node.id });
  
              animateMotion.append(mpath);
  
              this.append(animateMotion);
              try {
                  animateMotion.node.beginElement();
              } catch (e) {
                  // Fallback for IE 9.
                  // Run the animation programatically if FakeSmile (`http://leunen.me/fakesmile/`) present
                  if (document.documentElement.getAttribute('smiling') === 'fake') {
  
                      // Register the animation. (See `https://answers.launchpad.net/smil/+question/203333`)
                      var animation = animateMotion.node;
                      animation.animators = [];
  
                      var animationID = animation.getAttribute('id');
                      if (animationID) id2anim[animationID] = animation;
  
                      var targets = getTargets(animation);
                      for (var i = 0, len = targets.length; i < len; i++) {
                          var target = targets[i];
                          var animator = new Animator(animation, target, i);
                          animators.push(animator);
                          animation.animators[i] = animator;
                          animator.register();
                      }
                  }
              }
          },
  
          hasClass: function(className) {
  
              return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.node.getAttribute('class'));
          },
  
          addClass: function(className) {
  
              if (!this.hasClass(className)) {
                  var prevClasses = this.node.getAttribute('class') || '';
                  this.node.setAttribute('class', (prevClasses + ' ' + className).trim());
              }
  
              return this;
          },
  
          removeClass: function(className) {
  
              if (this.hasClass(className)) {
                  var newClasses = this.node.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2');
                  this.node.setAttribute('class', newClasses);
              }
  
              return this;
          },
  
          toggleClass: function(className, toAdd) {
  
              var toRemove = typeof toAdd === 'undefined' ? this.hasClass(className) : !toAdd;
  
              if (toRemove) {
                  this.removeClass(className);
              } else {
                  this.addClass(className);
              }
  
              return this;
          },
  
          // Interpolate path by discrete points. The precision of the sampling
          // is controlled by `interval`. In other words, `sample()` will generate
          // a point on the path starting at the beginning of the path going to the end
          // every `interval` pixels.
          // The sampler can be very useful for e.g. finding intersection between two
          // paths (finding the two closest points from two samples).
          sample: function(interval) {
  
              interval = interval || 1;
              var node = this.node;
              var length = node.getTotalLength();
              var samples = [];
              var distance = 0;
              var sample;
              while (distance < length) {
                  sample = node.getPointAtLength(distance);
                  samples.push({ x: sample.x, y: sample.y, distance: distance });
                  distance += interval;
              }
              return samples;
          },
  
          convertToPath: function() {
  
              var path = createElement('path');
              path.attr(this.attr());
              var d = this.convertToPathData();
              if (d) {
                  path.attr('d', d);
              }
              return path;
          },
  
          convertToPathData: function() {
  
              var tagName = this.node.tagName.toUpperCase();
  
              switch (tagName) {
              case 'PATH':
                  return this.attr('d');
              case 'LINE':
                  return convertLineToPathData(this.node);
              case 'POLYGON':
                  return convertPolygonToPathData(this.node);
              case 'POLYLINE':
                  return convertPolylineToPathData(this.node);
              case 'ELLIPSE':
                  return convertEllipseToPathData(this.node);
              case 'CIRCLE':
                  return convertCircleToPathData(this.node);
              case 'RECT':
                  return convertRectToPathData(this.node);
              }
  
              throw new Error(tagName + ' cannot be converted to PATH.');
          },
  
          // Find the intersection of a line starting in the center
          // of the SVG `node` ending in the point `ref`.
          // `target` is an SVG element to which `node`s transformations are relative to.
          // In JointJS, `target` is the `paper.viewport` SVG group element.
          // Note that `ref` point must be in the coordinate system of the `target` for this function to work properly.
          // Returns a point in the `target` coordinte system (the same system as `ref` is in) if
          // an intersection is found. Returns `undefined` otherwise.
          findIntersection: function(ref, target) {
  
              var svg = this.svg().node;
              target = target || svg;
              var bbox = g.rect(this.bbox(false, target));
              var center = bbox.center();
              var spot = bbox.intersectionWithLineFromCenterToPoint(ref);
  
              if (!spot) return undefined;
  
              var tagName = this.node.localName.toUpperCase();
  
              // Little speed up optimalization for `<rect>` element. We do not do conversion
              // to path element and sampling but directly calculate the intersection through
              // a transformed geometrical rectangle.
              if (tagName === 'RECT') {
  
                  var gRect = g.rect(
                      parseFloat(this.attr('x') || 0),
                      parseFloat(this.attr('y') || 0),
                      parseFloat(this.attr('width')),
                      parseFloat(this.attr('height'))
                  );
                  // Get the rect transformation matrix with regards to the SVG document.
                  var rectMatrix = this.getTransformToElement(target);
                  // Decompose the matrix to find the rotation angle.
                  var rectMatrixComponents = V.decomposeMatrix(rectMatrix);
                  // Now we want to rotate the rectangle back so that we
                  // can use `intersectionWithLineFromCenterToPoint()` passing the angle as the second argument.
                  var resetRotation = svg.createSVGTransform();
                  resetRotation.setRotate(-rectMatrixComponents.rotation, center.x, center.y);
                  var rect = V.transformRect(gRect, resetRotation.matrix.multiply(rectMatrix));
                  spot = g.rect(rect).intersectionWithLineFromCenterToPoint(ref, rectMatrixComponents.rotation);
  
              } else if (tagName === 'PATH' || tagName === 'POLYGON' || tagName === 'POLYLINE' || tagName === 'CIRCLE' || tagName === 'ELLIPSE') {
  
                  var pathNode = (tagName === 'PATH') ? this : this.convertToPath();
                  var samples = pathNode.sample();
                  var minDistance = Infinity;
                  var closestSamples = [];
  
                  for (var i = 0, len = samples.length; i < len; i++) {
  
                      var sample = samples[i];
                      // Convert the sample point in the local coordinate system to the global coordinate system.
                      var gp = V.createSVGPoint(sample.x, sample.y);
                      gp = gp.matrixTransform(this.getTransformToElement(target));
                      sample = g.point(gp);
                      var centerDistance = sample.distance(center);
                      // Penalize a higher distance to the reference point by 10%.
                      // This gives better results. This is due to
                      // inaccuracies introduced by rounding errors and getPointAtLength() returns.
                      var refDistance = sample.distance(ref) * 1.1;
                      var distance = centerDistance + refDistance;
                      if (distance < minDistance) {
                          minDistance = distance;
                          closestSamples = [{ sample: sample, refDistance: refDistance }];
                      } else if (distance < minDistance + 1) {
                          closestSamples.push({ sample: sample, refDistance: refDistance });
                      }
                  }
                  closestSamples.sort(function(a, b) { return a.refDistance - b.refDistance; });
                  spot = closestSamples[0].sample;
              }
  
              return spot;
          }
      };
  
      function convertLineToPathData(line) {
  
          line = createElement(line);
          var d = [
              'M', line.attr('x1'), line.attr('y1'),
              'L', line.attr('x2'), line.attr('y2')
          ].join(' ');
          return d;
      }
  
      function convertPolygonToPathData(polygon) {
  
          polygon = createElement(polygon);
          var points = polygon.node.points;
  
          var d = [];
          var p;
          for (var i = 0; i < points.length; i++) {
              p = points[i];
              d.push(i === 0 ? 'M' : 'L', p.x, p.y);
          }
          d.push('Z');
          return d.join(' ');
      }
  
      function convertPolylineToPathData(polyline) {
  
          polyline = createElement(polyline);
          var points = polyline.node.points;
  
          var d = [];
          var p;
          for (var i = 0; i < points.length; i++) {
              p = points[i];
              d.push(i === 0 ? 'M' : 'L', p.x, p.y);
          }
          return d.join(' ');
      }
  
      var KAPPA = 0.5522847498307935;
  
      function convertCircleToPathData(circle) {
  
          circle = createElement(circle);
          var cx = parseFloat(circle.attr('cx')) || 0;
          var cy = parseFloat(circle.attr('cy')) || 0;
          var r = parseFloat(circle.attr('r'));
          var cd = r * KAPPA; // Control distance.
  
          var d = [
              'M', cx, cy - r,    // Move to the first point.
              'C', cx + cd, cy - r, cx + r, cy - cd, cx + r, cy, // I. Quadrant.
              'C', cx + r, cy + cd, cx + cd, cy + r, cx, cy + r, // II. Quadrant.
              'C', cx - cd, cy + r, cx - r, cy + cd, cx - r, cy, // III. Quadrant.
              'C', cx - r, cy - cd, cx - cd, cy - r, cx, cy - r, // IV. Quadrant.
              'Z'
          ].join(' ');
          return d;
      }
  
      function convertEllipseToPathData(ellipse) {
  
          ellipse = createElement(ellipse);
          var cx = parseFloat(ellipse.attr('cx')) || 0;
          var cy = parseFloat(ellipse.attr('cy')) || 0;
          var rx = parseFloat(ellipse.attr('rx'));
          var ry = parseFloat(ellipse.attr('ry')) || rx;
          var cdx = rx * KAPPA; // Control distance x.
          var cdy = ry * KAPPA; // Control distance y.
  
          var d = [
              'M', cx, cy - ry,    // Move to the first point.
              'C', cx + cdx, cy - ry, cx + rx, cy - cdy, cx + rx, cy, // I. Quadrant.
              'C', cx + rx, cy + cdy, cx + cdx, cy + ry, cx, cy + ry, // II. Quadrant.
              'C', cx - cdx, cy + ry, cx - rx, cy + cdy, cx - rx, cy, // III. Quadrant.
              'C', cx - rx, cy - cdy, cx - cdx, cy - ry, cx, cy - ry, // IV. Quadrant.
              'Z'
          ].join(' ');
          return d;
      }
  
      function convertRectToPathData(rect) {
  
          rect = createElement(rect);
          var x = parseFloat(rect.attr('x')) || 0;
          var y = parseFloat(rect.attr('y')) || 0;
          var width = parseFloat(rect.attr('width')) || 0;
          var height = parseFloat(rect.attr('height')) || 0;
          var rx = parseFloat(rect.attr('rx')) || 0;
          var ry = parseFloat(rect.attr('ry')) || 0;
          var bbox = g.rect(x, y, width, height);
  
          var d;
  
          if (!rx && !ry) {
  
              d = [
                  'M', bbox.origin().x, bbox.origin().y,
                  'H', bbox.corner().x,
                  'V', bbox.corner().y,
                  'H', bbox.origin().x,
                  'V', bbox.origin().y,
                  'Z'
              ].join(' ');
  
          } else {
  
              var r = x + width;
              var b = y + height;
              d = [
                  'M', x + rx, y,
                  'L', r - rx, y,
                  'Q', r, y, r, y + ry,
                  'L', r, y + height - ry,
                  'Q', r, b, r - rx, b,
                  'L', x + rx, b,
                  'Q', x, b, x, b - rx,
                  'L', x, y + ry,
                  'Q', x, y, x + rx, y,
                  'Z'
              ].join(' ');
          }
          return d;
      }
  
      // Convert a rectangle to SVG path commands. `r` is an object of the form:
      // `{ x: [number], y: [number], width: [number], height: [number], top-ry: [number], top-ry: [number], bottom-rx: [number], bottom-ry: [number] }`,
      // where `x, y, width, height` are the usual rectangle attributes and [top-/bottom-]rx/ry allows for
      // specifying radius of the rectangle for all its sides (as opposed to the built-in SVG rectangle
      // that has only `rx` and `ry` attributes).
      function rectToPath(r) {
  
          var topRx = r.rx || r['top-rx'] || 0;
          var bottomRx = r.rx || r['bottom-rx'] || 0;
          var topRy = r.ry || r['top-ry'] || 0;
          var bottomRy = r.ry || r['bottom-ry'] || 0;
  
          return [
              'M', r.x, r.y + topRy,
              'v', r.height - topRy - bottomRy,
              'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy,
              'h', r.width - 2 * bottomRx,
              'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy,
              'v', -(r.height - bottomRy - topRy),
              'a', topRx, topRy, 0, 0, 0, -topRx, -topRy,
              'h', -(r.width - 2 * topRx),
              'a', topRx, topRy, 0, 0, 0, -topRx, topRy
          ].join(' ');
      }
  
      var V = createElement;
  
      V.isVElement = function(object) {
          return object instanceof VElement;
      };
  
      V.decomposeMatrix = decomposeMatrix;
      V.rectToPath = rectToPath;
  
      var svgDocument = V('svg').node;
  
      V.createSVGMatrix = function(m) {
  
          var svgMatrix = svgDocument.createSVGMatrix();
          for (var component in m) {
              svgMatrix[component] = m[component];
          }
  
          return svgMatrix;
      };
  
      V.createSVGTransform = function() {
  
          return svgDocument.createSVGTransform();
      };
  
      V.createSVGPoint = function(x, y) {
  
          var p = svgDocument.createSVGPoint();
          p.x = x;
          p.y = y;
          return p;
      };
  
      V.transformRect = function(r, matrix) {
  
          var p = svgDocument.createSVGPoint();
  
          p.x = r.x;
          p.y = r.y;
          var corner1 = p.matrixTransform(matrix);
  
          p.x = r.x + r.width;
          p.y = r.y;
          var corner2 = p.matrixTransform(matrix);
  
          p.x = r.x + r.width;
          p.y = r.y + r.height;
          var corner3 = p.matrixTransform(matrix);
  
          p.x = r.x;
          p.y = r.y + r.height;
          var corner4 = p.matrixTransform(matrix);
  
          var minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x);
          var maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x);
          var minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y);
          var maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y);
  
          return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
      };
  
      V.transformPoint = function(p, matrix) {
  
          return V.createSVGPoint(p.x, p.y).matrixTransform(matrix);
      };
  
      // Convert a style represented as string (e.g. `'fill="blue"; stroke="red"'`) to
      // an object (`{ fill: 'blue', stroke: 'red' }`).
      V.styleToObject = function(styleString) {
          var ret = {};
          var styles = styleString.split(';');
          for (var i = 0; i < styles.length; i++) {
              var style = styles[i];
              var pair = style.split('=');
              ret[pair[0].trim()] = pair[1].trim();
          }
          return ret;
      };
  
      // Inspired by d3.js https://github.com/mbostock/d3/blob/master/src/svg/arc.js
      V.createSlicePathData = function(innerRadius, outerRadius, startAngle, endAngle) {
  
          var svgArcMax = 2 * Math.PI - 1e-6;
          var r0 = innerRadius;
          var r1 = outerRadius;
          var a0 = startAngle;
          var a1 = endAngle;
          var da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0);
          var df = da < Math.PI ? '0' : '1';
          var c0 = Math.cos(a0);
          var s0 = Math.sin(a0);
          var c1 = Math.cos(a1);
          var s1 = Math.sin(a1);
  
          return (da >= svgArcMax)
              ? (r0
                 ? 'M0,' + r1
                 + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1)
                 + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1
                 + 'M0,' + r0
                 + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + (-r0)
                 + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + r0
                 + 'Z'
                 : 'M0,' + r1
                 + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1)
                 + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1
                 + 'Z')
              : (r0
                 ? 'M' + r1 * c0 + ',' + r1 * s0
                 + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1
                 + 'L' + r0 * c1 + ',' + r0 * s1
                 + 'A' + r0 + ',' + r0 + ' 0 ' + df + ',0 ' + r0 * c0 + ',' + r0 * s0
                 + 'Z'
                 : 'M' + r1 * c0 + ',' + r1 * s0
                 + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1
                 + 'L0,0'
                 + 'Z');
      };
  
      // Merge attributes from object `b` with attributes in object `a`.
      // Note that this modifies the object `a`.
      // Also important to note that attributes are merged but CSS classes are concatenated.
      V.mergeAttrs = function(a, b) {
          for (var attr in b) {
              if (attr === 'class') {
                  // Concatenate classes.
                  a[attr] = a[attr] ? a[attr] + ' ' + b[attr] : b[attr];
              } else if (attr === 'style') {
                  // `style` attribute can be an object.
                  if (isObject(a[attr]) && isObject(b[attr])) {
                      // `style` stored in `a` is an object.
                      a[attr] = V.mergeAttrs(a[attr], b[attr]);
                  } else if (isObject(a[attr])) {
                      // `style` in `a` is an object but it's a string in `b`.
                      // Convert the style represented as a string to an object in `b`.
                      a[attr] = V.mergeAttrs(a[attr], V.styleToObject(b[attr]));
                  } else if (isObject(b[attr])) {
                      // `style` in `a` is a string, in `b` it's an object.
                      a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), b[attr]);
                  } else {
                      // Both styles are strings.
                      a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), V.styleToObject(b[attr]));
                  }
              } else {
                  a[attr] = b[attr];
              }
          }
          return a;
      };
  
      V.annotateString = function(t, annotations, opt) {
  
          annotations = annotations || [];
          opt = opt || {};
          offset = opt.offset || 0;
          var compacted = [];
          var batch;
  
          var ret = [];
          var item;
          var prev;
  
          for (var i = 0; i < t.length; i++) {
  
              item = ret[i] = t[i];
  
              for (var j = 0; j < annotations.length; j++) {
                  var annotation = annotations[j];
                  var start = annotation.start + offset;
                  var end = annotation.end + offset;
  
                  if (i >= start && i < end) {
                      // Annotation applies.
                      if (isObject(item)) {
                          // There is more than one annotation to be applied => Merge attributes.
                          item.attrs = V.mergeAttrs(V.mergeAttrs({}, item.attrs), annotation.attrs);
                      } else {
                          item = ret[i] = { t: t[i], attrs: annotation.attrs };
                      }
                      if (opt.includeAnnotationIndices) {
                          (item.annotations || (item.annotations = [])).push(j);
                      }
                  }
              }
  
              prev = ret[i - 1];
  
              if (!prev) {
  
                  batch = item;
  
              } else if (isObject(item) && isObject(prev)) {
                  // Both previous item and the current one are annotations. If the attributes
                  // didn't change, merge the text.
                  if (JSON.stringify(item.attrs) === JSON.stringify(prev.attrs)) {
                      batch.t += item.t;
                  } else {
                      compacted.push(batch);
                      batch = item;
                  }
  
              } else if (isObject(item)) {
                  // Previous item was a string, current item is an annotation.
                  compacted.push(batch);
                  batch = item;
  
              } else if (isObject(prev)) {
                  // Previous item was an annotation, current item is a string.
                  compacted.push(batch);
                  batch = item;
  
              } else {
                  // Both previous and current item are strings.
                  batch = (batch || '') + item;
              }
          }
  
          if (batch) {
              compacted.push(batch);
          }
  
          return compacted;
      };
  
      V.findAnnotationsAtIndex = function(annotations, index) {
  
          if (!annotations) return [];
  
          var found = [];
  
          annotations.forEach(function(annotation) {
  
              if (annotation.start < index && index <= annotation.end) {
                  found.push(annotation);
              }
          });
          return found;
      };
  
      V.findAnnotationsBetweenIndexes = function(annotations, start, end) {
  
          if (!annotations) return [];
  
          var found = [];
  
          annotations.forEach(function(annotation) {
  
              if ((start >= annotation.start && start < annotation.end) || (end > annotation.start && end <= annotation.end) || (annotation.start >= start && annotation.end < end)) {
                  found.push(annotation);
              }
          });
          return found;
      };
  
      // Shift all the text annotations after character `index` by `offset` positions.
      V.shiftAnnotations = function(annotations, index, offset) {
  
          if (!annotations) return annotations;
  
          annotations.forEach(function(annotation) {
  
              if (annotation.start < index && annotation.end >= index) {
                  annotation.end += offset;
              } else if (annotation.start >= index) {
                  annotation.start += offset;
                  annotation.end += offset;
              }
          });
  
          return annotations;
      };
  
      V.sanitizeText = sanitizeText;
  
      return V;
  
  })();
  
  
      return V;
  
  }));
  