/*! JointJS v3.7.1 (2023-04-28) - JavaScript diagramming library


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 (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('backbone'), require('jquery')) :
	typeof define === 'function' && define.amd ? define(['exports', 'backbone', 'jquery'], factory) :
	(global = global || self, factory(global.joint = {}, global.Backbone, global.$));
}(this, function (exports, Backbone, $) { 'use strict';

	Backbone = Backbone && Backbone.hasOwnProperty('default') ? Backbone['default'] : Backbone;
	$ = $ && $.hasOwnProperty('default') ? $['default'] : $;

	var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

	function commonjsRequire () {
		throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
	}

	function unwrapExports (x) {
		return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x.default : x;
	}

	function createCommonjsModule(fn, module) {
		return module = { exports: {} }, fn(module, module.exports), module.exports;
	}

	function getCjsExportFromNamespace (n) {
		return n && n.default || n;
	}

	var check = function (it) {
	  return it && it.Math == Math && it;
	};

	// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028
	var global_1 =
	  // eslint-disable-next-line no-undef
	  check(typeof globalThis == 'object' && globalThis) ||
	  check(typeof window == 'object' && window) ||
	  check(typeof self == 'object' && self) ||
	  check(typeof commonjsGlobal == 'object' && commonjsGlobal) ||
	  // eslint-disable-next-line no-new-func
	  (function () { return this; })() || Function('return this')();

	var fails = function (exec) {
	  try {
	    return !!exec();
	  } catch (error) {
	    return true;
	  }
	};

	// Detect IE8's incomplete defineProperty implementation
	var descriptors = !fails(function () {
	  return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7;
	});

	'use strict';
	var nativePropertyIsEnumerable = {}.propertyIsEnumerable;
	var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;

	// Nashorn ~ JDK8 bug
	var NASHORN_BUG = getOwnPropertyDescriptor && !nativePropertyIsEnumerable.call({ 1: 2 }, 1);

	// `Object.prototype.propertyIsEnumerable` method implementation
	// https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
	var f = NASHORN_BUG ? function propertyIsEnumerable(V) {
	  var descriptor = getOwnPropertyDescriptor(this, V);
	  return !!descriptor && descriptor.enumerable;
	} : nativePropertyIsEnumerable;

	var objectPropertyIsEnumerable = {
		f: f
	};

	var createPropertyDescriptor = function (bitmap, value) {
	  return {
	    enumerable: !(bitmap & 1),
	    configurable: !(bitmap & 2),
	    writable: !(bitmap & 4),
	    value: value
	  };
	};

	var toString = {}.toString;

	var classofRaw = function (it) {
	  return toString.call(it).slice(8, -1);
	};

	var split = ''.split;

	// fallback for non-array-like ES3 and non-enumerable old V8 strings
	var indexedObject = fails(function () {
	  // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346
	  // eslint-disable-next-line no-prototype-builtins
	  return !Object('z').propertyIsEnumerable(0);
	}) ? function (it) {
	  return classofRaw(it) == 'String' ? split.call(it, '') : Object(it);
	} : Object;

	// `RequireObjectCoercible` abstract operation
	// https://tc39.es/ecma262/#sec-requireobjectcoercible
	var requireObjectCoercible = function (it) {
	  if (it == undefined) { throw TypeError("Can't call method on " + it); }
	  return it;
	};

	// toObject with fallback for non-array-like ES3 strings



	var toIndexedObject = function (it) {
	  return indexedObject(requireObjectCoercible(it));
	};

	var isObject = function (it) {
	  return typeof it === 'object' ? it !== null : typeof it === 'function';
	};

	// `ToPrimitive` abstract operation
	// https://tc39.es/ecma262/#sec-toprimitive
	// instead of the ES6 spec version, we didn't implement @@toPrimitive case
	// and the second argument - flag - preferred type is a string
	var toPrimitive = function (input, PREFERRED_STRING) {
	  if (!isObject(input)) { return input; }
	  var fn, val;
	  if (PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) { return val; }
	  if (typeof (fn = input.valueOf) == 'function' && !isObject(val = fn.call(input))) { return val; }
	  if (!PREFERRED_STRING && typeof (fn = input.toString) == 'function' && !isObject(val = fn.call(input))) { return val; }
	  throw TypeError("Can't convert object to primitive value");
	};

	var hasOwnProperty$1 = {}.hasOwnProperty;

	var has = function (it, key) {
	  return hasOwnProperty$1.call(it, key);
	};

	var document$1 = global_1.document;
	// typeof document.createElement is 'object' in old IE
	var EXISTS = isObject(document$1) && isObject(document$1.createElement);

	var documentCreateElement = function (it) {
	  return EXISTS ? document$1.createElement(it) : {};
	};

	// Thank's IE8 for his funny defineProperty
	var ie8DomDefine = !descriptors && !fails(function () {
	  return Object.defineProperty(documentCreateElement('div'), 'a', {
	    get: function () { return 7; }
	  }).a != 7;
	});

	var nativeGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;

	// `Object.getOwnPropertyDescriptor` method
	// https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
	var f$1 = descriptors ? nativeGetOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) {
	  O = toIndexedObject(O);
	  P = toPrimitive(P, true);
	  if (ie8DomDefine) { try {
	    return nativeGetOwnPropertyDescriptor(O, P);
	  } catch (error) { /* empty */ } }
	  if (has(O, P)) { return createPropertyDescriptor(!objectPropertyIsEnumerable.f.call(O, P), O[P]); }
	};

	var objectGetOwnPropertyDescriptor = {
		f: f$1
	};

	var anObject = function (it) {
	  if (!isObject(it)) {
	    throw TypeError(String(it) + ' is not an object');
	  } return it;
	};

	var nativeDefineProperty = Object.defineProperty;

	// `Object.defineProperty` method
	// https://tc39.es/ecma262/#sec-object.defineproperty
	var f$2 = descriptors ? nativeDefineProperty : function defineProperty(O, P, Attributes) {
	  anObject(O);
	  P = toPrimitive(P, true);
	  anObject(Attributes);
	  if (ie8DomDefine) { try {
	    return nativeDefineProperty(O, P, Attributes);
	  } catch (error) { /* empty */ } }
	  if ('get' in Attributes || 'set' in Attributes) { throw TypeError('Accessors not supported'); }
	  if ('value' in Attributes) { O[P] = Attributes.value; }
	  return O;
	};

	var objectDefineProperty = {
		f: f$2
	};

	var createNonEnumerableProperty = descriptors ? function (object, key, value) {
	  return objectDefineProperty.f(object, key, createPropertyDescriptor(1, value));
	} : function (object, key, value) {
	  object[key] = value;
	  return object;
	};

	var setGlobal = function (key, value) {
	  try {
	    createNonEnumerableProperty(global_1, key, value);
	  } catch (error) {
	    global_1[key] = value;
	  } return value;
	};

	var SHARED = '__core-js_shared__';
	var store = global_1[SHARED] || setGlobal(SHARED, {});

	var sharedStore = store;

	var functionToString = Function.toString;

	// this helper broken in `3.4.1-3.4.4`, so we can't use `shared` helper
	if (typeof sharedStore.inspectSource != 'function') {
	  sharedStore.inspectSource = function (it) {
	    return functionToString.call(it);
	  };
	}

	var inspectSource = sharedStore.inspectSource;

	var WeakMap = global_1.WeakMap;

	var nativeWeakMap = typeof WeakMap === 'function' && /native code/.test(inspectSource(WeakMap));

	var isPure = false;

	var shared = createCommonjsModule(function (module) {
	(module.exports = function (key, value) {
	  return sharedStore[key] || (sharedStore[key] = value !== undefined ? value : {});
	})('versions', []).push({
	  version: '3.8.3',
	  mode: isPure ? 'pure' : 'global',
	  copyright: '© 2021 Denis Pushkarev (zloirock.ru)'
	});
	});

	var id = 0;
	var postfix = Math.random();

	var uid = function (key) {
	  return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id + postfix).toString(36);
	};

	var keys = shared('keys');

	var sharedKey = function (key) {
	  return keys[key] || (keys[key] = uid(key));
	};

	var hiddenKeys = {};

	var WeakMap$1 = global_1.WeakMap;
	var set, get, has$1;

	var enforce = function (it) {
	  return has$1(it) ? get(it) : set(it, {});
	};

	var getterFor = function (TYPE) {
	  return function (it) {
	    var state;
	    if (!isObject(it) || (state = get(it)).type !== TYPE) {
	      throw TypeError('Incompatible receiver, ' + TYPE + ' required');
	    } return state;
	  };
	};

	if (nativeWeakMap) {
	  var store$1 = sharedStore.state || (sharedStore.state = new WeakMap$1());
	  var wmget = store$1.get;
	  var wmhas = store$1.has;
	  var wmset = store$1.set;
	  set = function (it, metadata) {
	    metadata.facade = it;
	    wmset.call(store$1, it, metadata);
	    return metadata;
	  };
	  get = function (it) {
	    return wmget.call(store$1, it) || {};
	  };
	  has$1 = function (it) {
	    return wmhas.call(store$1, it);
	  };
	} else {
	  var STATE = sharedKey('state');
	  hiddenKeys[STATE] = true;
	  set = function (it, metadata) {
	    metadata.facade = it;
	    createNonEnumerableProperty(it, STATE, metadata);
	    return metadata;
	  };
	  get = function (it) {
	    return has(it, STATE) ? it[STATE] : {};
	  };
	  has$1 = function (it) {
	    return has(it, STATE);
	  };
	}

	var internalState = {
	  set: set,
	  get: get,
	  has: has$1,
	  enforce: enforce,
	  getterFor: getterFor
	};
	var internalState_1 = internalState.set;
	var internalState_2 = internalState.get;
	var internalState_3 = internalState.has;
	var internalState_4 = internalState.enforce;
	var internalState_5 = internalState.getterFor;

	var redefine = createCommonjsModule(function (module) {
	var getInternalState = internalState.get;
	var enforceInternalState = internalState.enforce;
	var TEMPLATE = String(String).split('String');

	(module.exports = function (O, key, value, options) {
	  var unsafe = options ? !!options.unsafe : false;
	  var simple = options ? !!options.enumerable : false;
	  var noTargetGet = options ? !!options.noTargetGet : false;
	  var state;
	  if (typeof value == 'function') {
	    if (typeof key == 'string' && !has(value, 'name')) {
	      createNonEnumerableProperty(value, 'name', key);
	    }
	    state = enforceInternalState(value);
	    if (!state.source) {
	      state.source = TEMPLATE.join(typeof key == 'string' ? key : '');
	    }
	  }
	  if (O === global_1) {
	    if (simple) { O[key] = value; }
	    else { setGlobal(key, value); }
	    return;
	  } else if (!unsafe) {
	    delete O[key];
	  } else if (!noTargetGet && O[key]) {
	    simple = true;
	  }
	  if (simple) { O[key] = value; }
	  else { createNonEnumerableProperty(O, key, value); }
	// add fake Function#toString for correct work wrapped methods / constructors with methods like LoDash isNative
	})(Function.prototype, 'toString', function toString() {
	  return typeof this == 'function' && getInternalState(this).source || inspectSource(this);
	});
	});

	var path = global_1;

	var aFunction = function (variable) {
	  return typeof variable == 'function' ? variable : undefined;
	};

	var getBuiltIn = function (namespace, method) {
	  return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global_1[namespace])
	    : path[namespace] && path[namespace][method] || global_1[namespace] && global_1[namespace][method];
	};

	var ceil = Math.ceil;
	var floor = Math.floor;

	// `ToInteger` abstract operation
	// https://tc39.es/ecma262/#sec-tointeger
	var toInteger = function (argument) {
	  return isNaN(argument = +argument) ? 0 : (argument > 0 ? floor : ceil)(argument);
	};

	var min = Math.min;

	// `ToLength` abstract operation
	// https://tc39.es/ecma262/#sec-tolength
	var toLength = function (argument) {
	  return argument > 0 ? min(toInteger(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991
	};

	var max = Math.max;
	var min$1 = Math.min;

	// Helper for a popular repeating case of the spec:
	// Let integer be ? ToInteger(index).
	// If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length).
	var toAbsoluteIndex = function (index, length) {
	  var integer = toInteger(index);
	  return integer < 0 ? max(integer + length, 0) : min$1(integer, length);
	};

	// `Array.prototype.{ indexOf, includes }` methods implementation
	var createMethod = function (IS_INCLUDES) {
	  return function ($this, el, fromIndex) {
	    var O = toIndexedObject($this);
	    var length = toLength(O.length);
	    var index = toAbsoluteIndex(fromIndex, length);
	    var value;
	    // Array#includes uses SameValueZero equality algorithm
	    // eslint-disable-next-line no-self-compare
	    if (IS_INCLUDES && el != el) { while (length > index) {
	      value = O[index++];
	      // eslint-disable-next-line no-self-compare
	      if (value != value) { return true; }
	    // Array#indexOf ignores holes, Array#includes - not
	    } } else { for (;length > index; index++) {
	      if ((IS_INCLUDES || index in O) && O[index] === el) { return IS_INCLUDES || index || 0; }
	    } } return !IS_INCLUDES && -1;
	  };
	};

	var arrayIncludes = {
	  // `Array.prototype.includes` method
	  // https://tc39.es/ecma262/#sec-array.prototype.includes
	  includes: createMethod(true),
	  // `Array.prototype.indexOf` method
	  // https://tc39.es/ecma262/#sec-array.prototype.indexof
	  indexOf: createMethod(false)
	};
	var arrayIncludes_1 = arrayIncludes.includes;
	var arrayIncludes_2 = arrayIncludes.indexOf;

	var indexOf = arrayIncludes.indexOf;


	var objectKeysInternal = function (object, names) {
	  var O = toIndexedObject(object);
	  var i = 0;
	  var result = [];
	  var key;
	  for (key in O) { !has(hiddenKeys, key) && has(O, key) && result.push(key); }
	  // Don't enum bug & hidden keys
	  while (names.length > i) { if (has(O, key = names[i++])) {
	    ~indexOf(result, key) || result.push(key);
	  } }
	  return result;
	};

	// IE8- don't enum bug keys
	var enumBugKeys = [
	  'constructor',
	  'hasOwnProperty',
	  'isPrototypeOf',
	  'propertyIsEnumerable',
	  'toLocaleString',
	  'toString',
	  'valueOf'
	];

	var hiddenKeys$1 = enumBugKeys.concat('length', 'prototype');

	// `Object.getOwnPropertyNames` method
	// https://tc39.es/ecma262/#sec-object.getownpropertynames
	var f$3 = Object.getOwnPropertyNames || function getOwnPropertyNames(O) {
	  return objectKeysInternal(O, hiddenKeys$1);
	};

	var objectGetOwnPropertyNames = {
		f: f$3
	};

	var f$4 = Object.getOwnPropertySymbols;

	var objectGetOwnPropertySymbols = {
		f: f$4
	};

	// all object keys, includes non-enumerable and symbols
	var ownKeys = getBuiltIn('Reflect', 'ownKeys') || function ownKeys(it) {
	  var keys = objectGetOwnPropertyNames.f(anObject(it));
	  var getOwnPropertySymbols = objectGetOwnPropertySymbols.f;
	  return getOwnPropertySymbols ? keys.concat(getOwnPropertySymbols(it)) : keys;
	};

	var copyConstructorProperties = function (target, source) {
	  var keys = ownKeys(source);
	  var defineProperty = objectDefineProperty.f;
	  var getOwnPropertyDescriptor = objectGetOwnPropertyDescriptor.f;
	  for (var i = 0; i < keys.length; i++) {
	    var key = keys[i];
	    if (!has(target, key)) { defineProperty(target, key, getOwnPropertyDescriptor(source, key)); }
	  }
	};

	var replacement = /#|\.prototype\./;

	var isForced = function (feature, detection) {
	  var value = data[normalize(feature)];
	  return value == POLYFILL ? true
	    : value == NATIVE ? false
	    : typeof detection == 'function' ? fails(detection)
	    : !!detection;
	};

	var normalize = isForced.normalize = function (string) {
	  return String(string).replace(replacement, '.').toLowerCase();
	};

	var data = isForced.data = {};
	var NATIVE = isForced.NATIVE = 'N';
	var POLYFILL = isForced.POLYFILL = 'P';

	var isForced_1 = isForced;

	var getOwnPropertyDescriptor$1 = objectGetOwnPropertyDescriptor.f;






	/*
	  options.target      - name of the target object
	  options.global      - target is the global object
	  options.stat        - export as static methods of target
	  options.proto       - export as prototype methods of target
	  options.real        - real prototype method for the `pure` version
	  options.forced      - export even if the native feature is available
	  options.bind        - bind methods to the target, required for the `pure` version
	  options.wrap        - wrap constructors to preventing global pollution, required for the `pure` version
	  options.unsafe      - use the simple assignment of property instead of delete + defineProperty
	  options.sham        - add a flag to not completely full polyfills
	  options.enumerable  - export as enumerable property
	  options.noTargetGet - prevent calling a getter on target
	*/
	var _export = function (options, source) {
	  var TARGET = options.target;
	  var GLOBAL = options.global;
	  var STATIC = options.stat;
	  var FORCED, target, key, targetProperty, sourceProperty, descriptor;
	  if (GLOBAL) {
	    target = global_1;
	  } else if (STATIC) {
	    target = global_1[TARGET] || setGlobal(TARGET, {});
	  } else {
	    target = (global_1[TARGET] || {}).prototype;
	  }
	  if (target) { for (key in source) {
	    sourceProperty = source[key];
	    if (options.noTargetGet) {
	      descriptor = getOwnPropertyDescriptor$1(target, key);
	      targetProperty = descriptor && descriptor.value;
	    } else { targetProperty = target[key]; }
	    FORCED = isForced_1(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced);
	    // contained in target
	    if (!FORCED && targetProperty !== undefined) {
	      if (typeof sourceProperty === typeof targetProperty) { continue; }
	      copyConstructorProperties(sourceProperty, targetProperty);
	    }
	    // add a flag to not completely full polyfills
	    if (options.sham || (targetProperty && targetProperty.sham)) {
	      createNonEnumerableProperty(sourceProperty, 'sham', true);
	    }
	    // extend global
	    redefine(target, key, sourceProperty, options);
	  } }
	};

	var nativeSymbol = !!Object.getOwnPropertySymbols && !fails(function () {
	  // Chrome 38 Symbol has incorrect toString conversion
	  // eslint-disable-next-line no-undef
	  return !String(Symbol());
	});

	var useSymbolAsUid = nativeSymbol
	  // eslint-disable-next-line no-undef
	  && !Symbol.sham
	  // eslint-disable-next-line no-undef
	  && typeof Symbol.iterator == 'symbol';

	var WellKnownSymbolsStore = shared('wks');
	var Symbol$1 = global_1.Symbol;
	var createWellKnownSymbol = useSymbolAsUid ? Symbol$1 : Symbol$1 && Symbol$1.withoutSetter || uid;

	var wellKnownSymbol = function (name) {
	  if (!has(WellKnownSymbolsStore, name)) {
	    if (nativeSymbol && has(Symbol$1, name)) { WellKnownSymbolsStore[name] = Symbol$1[name]; }
	    else { WellKnownSymbolsStore[name] = createWellKnownSymbol('Symbol.' + name); }
	  } return WellKnownSymbolsStore[name];
	};

	// `Object.keys` method
	// https://tc39.es/ecma262/#sec-object.keys
	var objectKeys = Object.keys || function keys(O) {
	  return objectKeysInternal(O, enumBugKeys);
	};

	// `Object.defineProperties` method
	// https://tc39.es/ecma262/#sec-object.defineproperties
	var objectDefineProperties = descriptors ? Object.defineProperties : function defineProperties(O, Properties) {
	  anObject(O);
	  var keys = objectKeys(Properties);
	  var length = keys.length;
	  var index = 0;
	  var key;
	  while (length > index) { objectDefineProperty.f(O, key = keys[index++], Properties[key]); }
	  return O;
	};

	var html = getBuiltIn('document', 'documentElement');

	var GT = '>';
	var LT = '<';
	var PROTOTYPE = 'prototype';
	var SCRIPT = 'script';
	var IE_PROTO = sharedKey('IE_PROTO');

	var EmptyConstructor = function () { /* empty */ };

	var scriptTag = function (content) {
	  return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT;
	};

	// Create object with fake `null` prototype: use ActiveX Object with cleared prototype
	var NullProtoObjectViaActiveX = function (activeXDocument) {
	  activeXDocument.write(scriptTag(''));
	  activeXDocument.close();
	  var temp = activeXDocument.parentWindow.Object;
	  activeXDocument = null; // avoid memory leak
	  return temp;
	};

	// Create object with fake `null` prototype: use iframe Object with cleared prototype
	var NullProtoObjectViaIFrame = function () {
	  // Thrash, waste and sodomy: IE GC bug
	  var iframe = documentCreateElement('iframe');
	  var JS = 'java' + SCRIPT + ':';
	  var iframeDocument;
	  iframe.style.display = 'none';
	  html.appendChild(iframe);
	  // https://github.com/zloirock/core-js/issues/475
	  iframe.src = String(JS);
	  iframeDocument = iframe.contentWindow.document;
	  iframeDocument.open();
	  iframeDocument.write(scriptTag('document.F=Object'));
	  iframeDocument.close();
	  return iframeDocument.F;
	};

	// Check for document.domain and active x support
	// No need to use active x approach when document.domain is not set
	// see https://github.com/es-shims/es5-shim/issues/150
	// variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346
	// avoid IE GC bug
	var activeXDocument;
	var NullProtoObject = function () {
	  try {
	    /* global ActiveXObject */
	    activeXDocument = document.domain && new ActiveXObject('htmlfile');
	  } catch (error) { /* ignore */ }
	  NullProtoObject = activeXDocument ? NullProtoObjectViaActiveX(activeXDocument) : NullProtoObjectViaIFrame();
	  var length = enumBugKeys.length;
	  while (length--) { delete NullProtoObject[PROTOTYPE][enumBugKeys[length]]; }
	  return NullProtoObject();
	};

	hiddenKeys[IE_PROTO] = true;

	// `Object.create` method
	// https://tc39.es/ecma262/#sec-object.create
	var objectCreate = Object.create || function create(O, Properties) {
	  var result;
	  if (O !== null) {
	    EmptyConstructor[PROTOTYPE] = anObject(O);
	    result = new EmptyConstructor();
	    EmptyConstructor[PROTOTYPE] = null;
	    // add "__proto__" for Object.getPrototypeOf polyfill
	    result[IE_PROTO] = O;
	  } else { result = NullProtoObject(); }
	  return Properties === undefined ? result : objectDefineProperties(result, Properties);
	};

	var UNSCOPABLES = wellKnownSymbol('unscopables');
	var ArrayPrototype = Array.prototype;

	// Array.prototype[@@unscopables]
	// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
	if (ArrayPrototype[UNSCOPABLES] == undefined) {
	  objectDefineProperty.f(ArrayPrototype, UNSCOPABLES, {
	    configurable: true,
	    value: objectCreate(null)
	  });
	}

	// add a key to Array.prototype[@@unscopables]
	var addToUnscopables = function (key) {
	  ArrayPrototype[UNSCOPABLES][key] = true;
	};

	var defineProperty = Object.defineProperty;
	var cache = {};

	var thrower = function (it) { throw it; };

	var arrayMethodUsesToLength = function (METHOD_NAME, options) {
	  if (has(cache, METHOD_NAME)) { return cache[METHOD_NAME]; }
	  if (!options) { options = {}; }
	  var method = [][METHOD_NAME];
	  var ACCESSORS = has(options, 'ACCESSORS') ? options.ACCESSORS : false;
	  var argument0 = has(options, 0) ? options[0] : thrower;
	  var argument1 = has(options, 1) ? options[1] : undefined;

	  return cache[METHOD_NAME] = !!method && !fails(function () {
	    if (ACCESSORS && !descriptors) { return true; }
	    var O = { length: -1 };

	    if (ACCESSORS) { defineProperty(O, 1, { enumerable: true, get: thrower }); }
	    else { O[1] = 1; }

	    method.call(O, argument0, argument1);
	  });
	};

	'use strict';

	var $includes = arrayIncludes.includes;



	var USES_TO_LENGTH = arrayMethodUsesToLength('indexOf', { ACCESSORS: true, 1: 0 });

	// `Array.prototype.includes` method
	// https://tc39.es/ecma262/#sec-array.prototype.includes
	_export({ target: 'Array', proto: true, forced: !USES_TO_LENGTH }, {
	  includes: function includes(el /* , fromIndex = 0 */) {
	    return $includes(this, el, arguments.length > 1 ? arguments[1] : undefined);
	  }
	});

	// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
	addToUnscopables('includes');

	var es_array_includes = {

	};

	var aFunction$1 = function (it) {
	  if (typeof it != 'function') {
	    throw TypeError(String(it) + ' is not a function');
	  } return it;
	};

	// optional / simple context binding
	var functionBindContext = function (fn, that, length) {
	  aFunction$1(fn);
	  if (that === undefined) { return fn; }
	  switch (length) {
	    case 0: return function () {
	      return fn.call(that);
	    };
	    case 1: return function (a) {
	      return fn.call(that, a);
	    };
	    case 2: return function (a, b) {
	      return fn.call(that, a, b);
	    };
	    case 3: return function (a, b, c) {
	      return fn.call(that, a, b, c);
	    };
	  }
	  return function (/* ...args */) {
	    return fn.apply(that, arguments);
	  };
	};

	var call = Function.call;

	var entryUnbind = function (CONSTRUCTOR, METHOD, length) {
	  return functionBindContext(call, global_1[CONSTRUCTOR].prototype[METHOD], length);
	};

	var includes = entryUnbind('Array', 'includes');

	var includes$1 = includes;

	// `ToObject` abstract operation
	// https://tc39.es/ecma262/#sec-toobject
	var toObject = function (argument) {
	  return Object(requireObjectCoercible(argument));
	};

	// `IsArray` abstract operation
	// https://tc39.es/ecma262/#sec-isarray
	var isArray = Array.isArray || function isArray(arg) {
	  return classofRaw(arg) == 'Array';
	};

	var SPECIES = wellKnownSymbol('species');

	// `ArraySpeciesCreate` abstract operation
	// https://tc39.es/ecma262/#sec-arrayspeciescreate
	var arraySpeciesCreate = function (originalArray, length) {
	  var C;
	  if (isArray(originalArray)) {
	    C = originalArray.constructor;
	    // cross-realm fallback
	    if (typeof C == 'function' && (C === Array || isArray(C.prototype))) { C = undefined; }
	    else if (isObject(C)) {
	      C = C[SPECIES];
	      if (C === null) { C = undefined; }
	    }
	  } return new (C === undefined ? Array : C)(length === 0 ? 0 : length);
	};

	var push = [].push;

	// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterOut }` methods implementation
	var createMethod$1 = function (TYPE) {
	  var IS_MAP = TYPE == 1;
	  var IS_FILTER = TYPE == 2;
	  var IS_SOME = TYPE == 3;
	  var IS_EVERY = TYPE == 4;
	  var IS_FIND_INDEX = TYPE == 6;
	  var IS_FILTER_OUT = TYPE == 7;
	  var NO_HOLES = TYPE == 5 || IS_FIND_INDEX;
	  return function ($this, callbackfn, that, specificCreate) {
	    var O = toObject($this);
	    var self = indexedObject(O);
	    var boundFunction = functionBindContext(callbackfn, that, 3);
	    var length = toLength(self.length);
	    var index = 0;
	    var create = specificCreate || arraySpeciesCreate;
	    var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_OUT ? create($this, 0) : undefined;
	    var value, result;
	    for (;length > index; index++) { if (NO_HOLES || index in self) {
	      value = self[index];
	      result = boundFunction(value, index, O);
	      if (TYPE) {
	        if (IS_MAP) { target[index] = result; } // map
	        else if (result) { switch (TYPE) {
	          case 3: return true;              // some
	          case 5: return value;             // find
	          case 6: return index;             // findIndex
	          case 2: push.call(target, value); // filter
	        } } else { switch (TYPE) {
	          case 4: return false;             // every
	          case 7: push.call(target, value); // filterOut
	        } }
	      }
	    } }
	    return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target;
	  };
	};

	var arrayIteration = {
	  // `Array.prototype.forEach` method
	  // https://tc39.es/ecma262/#sec-array.prototype.foreach
	  forEach: createMethod$1(0),
	  // `Array.prototype.map` method
	  // https://tc39.es/ecma262/#sec-array.prototype.map
	  map: createMethod$1(1),
	  // `Array.prototype.filter` method
	  // https://tc39.es/ecma262/#sec-array.prototype.filter
	  filter: createMethod$1(2),
	  // `Array.prototype.some` method
	  // https://tc39.es/ecma262/#sec-array.prototype.some
	  some: createMethod$1(3),
	  // `Array.prototype.every` method
	  // https://tc39.es/ecma262/#sec-array.prototype.every
	  every: createMethod$1(4),
	  // `Array.prototype.find` method
	  // https://tc39.es/ecma262/#sec-array.prototype.find
	  find: createMethod$1(5),
	  // `Array.prototype.findIndex` method
	  // https://tc39.es/ecma262/#sec-array.prototype.findIndex
	  findIndex: createMethod$1(6),
	  // `Array.prototype.filterOut` method
	  // https://github.com/tc39/proposal-array-filtering
	  filterOut: createMethod$1(7)
	};
	var arrayIteration_1 = arrayIteration.forEach;
	var arrayIteration_2 = arrayIteration.map;
	var arrayIteration_3 = arrayIteration.filter;
	var arrayIteration_4 = arrayIteration.some;
	var arrayIteration_5 = arrayIteration.every;
	var arrayIteration_6 = arrayIteration.find;
	var arrayIteration_7 = arrayIteration.findIndex;
	var arrayIteration_8 = arrayIteration.filterOut;

	'use strict';

	var $find = arrayIteration.find;



	var FIND = 'find';
	var SKIPS_HOLES = true;

	var USES_TO_LENGTH$1 = arrayMethodUsesToLength(FIND);

	// Shouldn't skip holes
	if (FIND in []) { Array(1)[FIND](function () { SKIPS_HOLES = false; }); }

	// `Array.prototype.find` method
	// https://tc39.es/ecma262/#sec-array.prototype.find
	_export({ target: 'Array', proto: true, forced: SKIPS_HOLES || !USES_TO_LENGTH$1 }, {
	  find: function find(callbackfn /* , that = undefined */) {
	    return $find(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
	  }
	});

	// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
	addToUnscopables(FIND);

	var es_array_find = {

	};

	var find = entryUnbind('Array', 'find');

	var find$1 = find;

	// `String.prototype.{ codePointAt, at }` methods implementation
	var createMethod$2 = function (CONVERT_TO_STRING) {
	  return function ($this, pos) {
	    var S = String(requireObjectCoercible($this));
	    var position = toInteger(pos);
	    var size = S.length;
	    var first, second;
	    if (position < 0 || position >= size) { return CONVERT_TO_STRING ? '' : undefined; }
	    first = S.charCodeAt(position);
	    return first < 0xD800 || first > 0xDBFF || position + 1 === size
	      || (second = S.charCodeAt(position + 1)) < 0xDC00 || second > 0xDFFF
	        ? CONVERT_TO_STRING ? S.charAt(position) : first
	        : CONVERT_TO_STRING ? S.slice(position, position + 2) : (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000;
	  };
	};

	var stringMultibyte = {
	  // `String.prototype.codePointAt` method
	  // https://tc39.es/ecma262/#sec-string.prototype.codepointat
	  codeAt: createMethod$2(false),
	  // `String.prototype.at` method
	  // https://github.com/mathiasbynens/String.prototype.at
	  charAt: createMethod$2(true)
	};
	var stringMultibyte_1 = stringMultibyte.codeAt;
	var stringMultibyte_2 = stringMultibyte.charAt;

	var correctPrototypeGetter = !fails(function () {
	  function F() { /* empty */ }
	  F.prototype.constructor = null;
	  return Object.getPrototypeOf(new F()) !== F.prototype;
	});

	var IE_PROTO$1 = sharedKey('IE_PROTO');
	var ObjectPrototype = Object.prototype;

	// `Object.getPrototypeOf` method
	// https://tc39.es/ecma262/#sec-object.getprototypeof
	var objectGetPrototypeOf = correctPrototypeGetter ? Object.getPrototypeOf : function (O) {
	  O = toObject(O);
	  if (has(O, IE_PROTO$1)) { return O[IE_PROTO$1]; }
	  if (typeof O.constructor == 'function' && O instanceof O.constructor) {
	    return O.constructor.prototype;
	  } return O instanceof Object ? ObjectPrototype : null;
	};

	'use strict';







	var ITERATOR = wellKnownSymbol('iterator');
	var BUGGY_SAFARI_ITERATORS = false;

	var returnThis = function () { return this; };

	// `%IteratorPrototype%` object
	// https://tc39.es/ecma262/#sec-%iteratorprototype%-object
	var IteratorPrototype, PrototypeOfArrayIteratorPrototype, arrayIterator;

	if ([].keys) {
	  arrayIterator = [].keys();
	  // Safari 8 has buggy iterators w/o `next`
	  if (!('next' in arrayIterator)) { BUGGY_SAFARI_ITERATORS = true; }
	  else {
	    PrototypeOfArrayIteratorPrototype = objectGetPrototypeOf(objectGetPrototypeOf(arrayIterator));
	    if (PrototypeOfArrayIteratorPrototype !== Object.prototype) { IteratorPrototype = PrototypeOfArrayIteratorPrototype; }
	  }
	}

	var NEW_ITERATOR_PROTOTYPE = IteratorPrototype == undefined || fails(function () {
	  var test = {};
	  // FF44- legacy iterators case
	  return IteratorPrototype[ITERATOR].call(test) !== test;
	});

	if (NEW_ITERATOR_PROTOTYPE) { IteratorPrototype = {}; }

	// 25.1.2.1.1 %IteratorPrototype%[@@iterator]()
	if ((!isPure || NEW_ITERATOR_PROTOTYPE) && !has(IteratorPrototype, ITERATOR)) {
	  createNonEnumerableProperty(IteratorPrototype, ITERATOR, returnThis);
	}

	var iteratorsCore = {
	  IteratorPrototype: IteratorPrototype,
	  BUGGY_SAFARI_ITERATORS: BUGGY_SAFARI_ITERATORS
	};
	var iteratorsCore_1 = iteratorsCore.IteratorPrototype;
	var iteratorsCore_2 = iteratorsCore.BUGGY_SAFARI_ITERATORS;

	var defineProperty$1 = objectDefineProperty.f;



	var TO_STRING_TAG = wellKnownSymbol('toStringTag');

	var setToStringTag = function (it, TAG, STATIC) {
	  if (it && !has(it = STATIC ? it : it.prototype, TO_STRING_TAG)) {
	    defineProperty$1(it, TO_STRING_TAG, { configurable: true, value: TAG });
	  }
	};

	var iterators = {};

	'use strict';
	var IteratorPrototype$1 = iteratorsCore.IteratorPrototype;





	var returnThis$1 = function () { return this; };

	var createIteratorConstructor = function (IteratorConstructor, NAME, next) {
	  var TO_STRING_TAG = NAME + ' Iterator';
	  IteratorConstructor.prototype = objectCreate(IteratorPrototype$1, { next: createPropertyDescriptor(1, next) });
	  setToStringTag(IteratorConstructor, TO_STRING_TAG, false, true);
	  iterators[TO_STRING_TAG] = returnThis$1;
	  return IteratorConstructor;
	};

	var aPossiblePrototype = function (it) {
	  if (!isObject(it) && it !== null) {
	    throw TypeError("Can't set " + String(it) + ' as a prototype');
	  } return it;
	};

	// `Object.setPrototypeOf` method
	// https://tc39.es/ecma262/#sec-object.setprototypeof
	// Works with __proto__ only. Old v8 can't work with null proto objects.
	/* eslint-disable no-proto */
	var objectSetPrototypeOf = Object.setPrototypeOf || ('__proto__' in {} ? function () {
	  var CORRECT_SETTER = false;
	  var test = {};
	  var setter;
	  try {
	    setter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set;
	    setter.call(test, []);
	    CORRECT_SETTER = test instanceof Array;
	  } catch (error) { /* empty */ }
	  return function setPrototypeOf(O, proto) {
	    anObject(O);
	    aPossiblePrototype(proto);
	    if (CORRECT_SETTER) { setter.call(O, proto); }
	    else { O.__proto__ = proto; }
	    return O;
	  };
	}() : undefined);

	'use strict';












	var IteratorPrototype$2 = iteratorsCore.IteratorPrototype;
	var BUGGY_SAFARI_ITERATORS$1 = iteratorsCore.BUGGY_SAFARI_ITERATORS;
	var ITERATOR$1 = wellKnownSymbol('iterator');
	var KEYS = 'keys';
	var VALUES = 'values';
	var ENTRIES = 'entries';

	var returnThis$2 = function () { return this; };

	var defineIterator = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, IS_SET, FORCED) {
	  createIteratorConstructor(IteratorConstructor, NAME, next);

	  var getIterationMethod = function (KIND) {
	    if (KIND === DEFAULT && defaultIterator) { return defaultIterator; }
	    if (!BUGGY_SAFARI_ITERATORS$1 && KIND in IterablePrototype) { return IterablePrototype[KIND]; }
	    switch (KIND) {
	      case KEYS: return function keys() { return new IteratorConstructor(this, KIND); };
	      case VALUES: return function values() { return new IteratorConstructor(this, KIND); };
	      case ENTRIES: return function entries() { return new IteratorConstructor(this, KIND); };
	    } return function () { return new IteratorConstructor(this); };
	  };

	  var TO_STRING_TAG = NAME + ' Iterator';
	  var INCORRECT_VALUES_NAME = false;
	  var IterablePrototype = Iterable.prototype;
	  var nativeIterator = IterablePrototype[ITERATOR$1]
	    || IterablePrototype['@@iterator']
	    || DEFAULT && IterablePrototype[DEFAULT];
	  var defaultIterator = !BUGGY_SAFARI_ITERATORS$1 && nativeIterator || getIterationMethod(DEFAULT);
	  var anyNativeIterator = NAME == 'Array' ? IterablePrototype.entries || nativeIterator : nativeIterator;
	  var CurrentIteratorPrototype, methods, KEY;

	  // fix native
	  if (anyNativeIterator) {
	    CurrentIteratorPrototype = objectGetPrototypeOf(anyNativeIterator.call(new Iterable()));
	    if (IteratorPrototype$2 !== Object.prototype && CurrentIteratorPrototype.next) {
	      if (!isPure && objectGetPrototypeOf(CurrentIteratorPrototype) !== IteratorPrototype$2) {
	        if (objectSetPrototypeOf) {
	          objectSetPrototypeOf(CurrentIteratorPrototype, IteratorPrototype$2);
	        } else if (typeof CurrentIteratorPrototype[ITERATOR$1] != 'function') {
	          createNonEnumerableProperty(CurrentIteratorPrototype, ITERATOR$1, returnThis$2);
	        }
	      }
	      // Set @@toStringTag to native iterators
	      setToStringTag(CurrentIteratorPrototype, TO_STRING_TAG, true, true);
	      if (isPure) { iterators[TO_STRING_TAG] = returnThis$2; }
	    }
	  }

	  // fix Array#{values, @@iterator}.name in V8 / FF
	  if (DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) {
	    INCORRECT_VALUES_NAME = true;
	    defaultIterator = function values() { return nativeIterator.call(this); };
	  }

	  // define iterator
	  if ((!isPure || FORCED) && IterablePrototype[ITERATOR$1] !== defaultIterator) {
	    createNonEnumerableProperty(IterablePrototype, ITERATOR$1, defaultIterator);
	  }
	  iterators[NAME] = defaultIterator;

	  // export additional methods
	  if (DEFAULT) {
	    methods = {
	      values: getIterationMethod(VALUES),
	      keys: IS_SET ? defaultIterator : getIterationMethod(KEYS),
	      entries: getIterationMethod(ENTRIES)
	    };
	    if (FORCED) { for (KEY in methods) {
	      if (BUGGY_SAFARI_ITERATORS$1 || INCORRECT_VALUES_NAME || !(KEY in IterablePrototype)) {
	        redefine(IterablePrototype, KEY, methods[KEY]);
	      }
	    } } else { _export({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS$1 || INCORRECT_VALUES_NAME }, methods); }
	  }

	  return methods;
	};

	'use strict';
	var charAt = stringMultibyte.charAt;



	var STRING_ITERATOR = 'String Iterator';
	var setInternalState = internalState.set;
	var getInternalState = internalState.getterFor(STRING_ITERATOR);

	// `String.prototype[@@iterator]` method
	// https://tc39.es/ecma262/#sec-string.prototype-@@iterator
	defineIterator(String, 'String', function (iterated) {
	  setInternalState(this, {
	    type: STRING_ITERATOR,
	    string: String(iterated),
	    index: 0
	  });
	// `%StringIteratorPrototype%.next` method
	// https://tc39.es/ecma262/#sec-%stringiteratorprototype%.next
	}, function next() {
	  var state = getInternalState(this);
	  var string = state.string;
	  var index = state.index;
	  var point;
	  if (index >= string.length) { return { value: undefined, done: true }; }
	  point = charAt(string, index);
	  state.index += point.length;
	  return { value: point, done: false };
	});

	var es_string_iterator = {

	};

	var iteratorClose = function (iterator) {
	  var returnMethod = iterator['return'];
	  if (returnMethod !== undefined) {
	    return anObject(returnMethod.call(iterator)).value;
	  }
	};

	// call something on iterator step with safe closing on error
	var callWithSafeIterationClosing = function (iterator, fn, value, ENTRIES) {
	  try {
	    return ENTRIES ? fn(anObject(value)[0], value[1]) : fn(value);
	  // 7.4.6 IteratorClose(iterator, completion)
	  } catch (error) {
	    iteratorClose(iterator);
	    throw error;
	  }
	};

	var ITERATOR$2 = wellKnownSymbol('iterator');
	var ArrayPrototype$1 = Array.prototype;

	// check on default Array iterator
	var isArrayIteratorMethod = function (it) {
	  return it !== undefined && (iterators.Array === it || ArrayPrototype$1[ITERATOR$2] === it);
	};

	'use strict';




	var createProperty = function (object, key, value) {
	  var propertyKey = toPrimitive(key);
	  if (propertyKey in object) { objectDefineProperty.f(object, propertyKey, createPropertyDescriptor(0, value)); }
	  else { object[propertyKey] = value; }
	};

	var TO_STRING_TAG$1 = wellKnownSymbol('toStringTag');
	var test = {};

	test[TO_STRING_TAG$1] = 'z';

	var toStringTagSupport = String(test) === '[object z]';

	var TO_STRING_TAG$2 = wellKnownSymbol('toStringTag');
	// ES3 wrong here
	var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) == 'Arguments';

	// fallback for IE11 Script Access Denied error
	var tryGet = function (it, key) {
	  try {
	    return it[key];
	  } catch (error) { /* empty */ }
	};

	// getting tag from ES6+ `Object.prototype.toString`
	var classof = toStringTagSupport ? classofRaw : function (it) {
	  var O, tag, result;
	  return it === undefined ? 'Undefined' : it === null ? 'Null'
	    // @@toStringTag case
	    : typeof (tag = tryGet(O = Object(it), TO_STRING_TAG$2)) == 'string' ? tag
	    // builtinTag case
	    : CORRECT_ARGUMENTS ? classofRaw(O)
	    // ES3 arguments fallback
	    : (result = classofRaw(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : result;
	};

	var ITERATOR$3 = wellKnownSymbol('iterator');

	var getIteratorMethod = function (it) {
	  if (it != undefined) { return it[ITERATOR$3]
	    || it['@@iterator']
	    || iterators[classof(it)]; }
	};

	'use strict';








	// `Array.from` method implementation
	// https://tc39.es/ecma262/#sec-array.from
	var arrayFrom = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) {
	  var O = toObject(arrayLike);
	  var C = typeof this == 'function' ? this : Array;
	  var argumentsLength = arguments.length;
	  var mapfn = argumentsLength > 1 ? arguments[1] : undefined;
	  var mapping = mapfn !== undefined;
	  var iteratorMethod = getIteratorMethod(O);
	  var index = 0;
	  var length, result, step, iterator, next, value;
	  if (mapping) { mapfn = functionBindContext(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2); }
	  // if the target is not iterable or it's an array with the default iterator - use a simple case
	  if (iteratorMethod != undefined && !(C == Array && isArrayIteratorMethod(iteratorMethod))) {
	    iterator = iteratorMethod.call(O);
	    next = iterator.next;
	    result = new C();
	    for (;!(step = next.call(iterator)).done; index++) {
	      value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value;
	      createProperty(result, index, value);
	    }
	  } else {
	    length = toLength(O.length);
	    result = new C(length);
	    for (;length > index; index++) {
	      value = mapping ? mapfn(O[index], index) : O[index];
	      createProperty(result, index, value);
	    }
	  }
	  result.length = index;
	  return result;
	};

	var ITERATOR$4 = wellKnownSymbol('iterator');
	var SAFE_CLOSING = false;

	try {
	  var called = 0;
	  var iteratorWithReturn = {
	    next: function () {
	      return { done: !!called++ };
	    },
	    'return': function () {
	      SAFE_CLOSING = true;
	    }
	  };
	  iteratorWithReturn[ITERATOR$4] = function () {
	    return this;
	  };
	  // eslint-disable-next-line no-throw-literal
	  Array.from(iteratorWithReturn, function () { throw 2; });
	} catch (error) { /* empty */ }

	var checkCorrectnessOfIteration = function (exec, SKIP_CLOSING) {
	  if (!SKIP_CLOSING && !SAFE_CLOSING) { return false; }
	  var ITERATION_SUPPORT = false;
	  try {
	    var object = {};
	    object[ITERATOR$4] = function () {
	      return {
	        next: function () {
	          return { done: ITERATION_SUPPORT = true };
	        }
	      };
	    };
	    exec(object);
	  } catch (error) { /* empty */ }
	  return ITERATION_SUPPORT;
	};

	var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function (iterable) {
	  Array.from(iterable);
	});

	// `Array.from` method
	// https://tc39.es/ecma262/#sec-array.from
	_export({ target: 'Array', stat: true, forced: INCORRECT_ITERATION }, {
	  from: arrayFrom
	});

	var es_array_from = {

	};

	var from_1 = path.Array.from;

	var from_1$1 = from_1;

	'use strict';

	var $findIndex = arrayIteration.findIndex;



	var FIND_INDEX = 'findIndex';
	var SKIPS_HOLES$1 = true;

	var USES_TO_LENGTH$2 = arrayMethodUsesToLength(FIND_INDEX);

	// Shouldn't skip holes
	if (FIND_INDEX in []) { Array(1)[FIND_INDEX](function () { SKIPS_HOLES$1 = false; }); }

	// `Array.prototype.findIndex` method
	// https://tc39.es/ecma262/#sec-array.prototype.findindex
	_export({ target: 'Array', proto: true, forced: SKIPS_HOLES$1 || !USES_TO_LENGTH$2 }, {
	  findIndex: function findIndex(callbackfn /* , that = undefined */) {
	    return $findIndex(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
	  }
	});

	// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
	addToUnscopables(FIND_INDEX);

	var es_array_findIndex = {

	};

	var findIndex = entryUnbind('Array', 'findIndex');

	var findIndex$1 = findIndex;

	var base64 = createCommonjsModule(function (module, exports) {
	(function() {

	    /**
	     * version: 0.3.0
	     * git://github.com/davidchambers/Base64.js.git
	     */

	    var object = 'object' != 'undefined' ? exports : this; // #8: web workers
	    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

	    function InvalidCharacterError(message) {
	        this.message = message;
	    }

	    InvalidCharacterError.prototype = new Error;
	    InvalidCharacterError.prototype.name = 'InvalidCharacterError';

	    // encoder
	    // [https://gist.github.com/999166] by [https://github.com/nignag]
	    object.btoa || (
	        object.btoa = function(input) {
	            var str = String(input);
	            for (
	                // initialize result and counter
	                var block, charCode, idx = 0, map = chars, output = '';
	                // if the next str index does not exist:
	                //   change the mapping table to "="
	                //   check if d has no fractional digits
	                str.charAt(idx | 0) || (map = '=', idx % 1);
	                // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
	                output += map.charAt(63 & block >> 8 - idx % 1 * 8)
	            ) {
	                charCode = str.charCodeAt(idx += 3 / 4);
	                if (charCode > 0xFF) {
	                    throw new InvalidCharacterError('\'btoa\' failed: The string to be encoded contains characters outside of the Latin1 range.');
	                }
	                block = block << 8 | charCode;
	            }
	            return output;
	        });

	    // decoder
	    // [https://gist.github.com/1020396] by [https://github.com/atk]
	    object.atob || (
	        object.atob = function(input) {
	            var str = String(input).replace(/=+$/, '');
	            if (str.length % 4 == 1) {
	                throw new InvalidCharacterError('\'atob\' failed: The string to be decoded is not correctly encoded.');
	            }
	            for (
	                // initialize result and counters
	                var bc = 0, bs, buffer, idx = 0, output = '';
	                // get next character
	                // eslint-disable-next-line no-cond-assign
	                buffer = str.charAt(idx++);
	                // character found in table? initialize bit storage and add its ascii value;
	                ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
	                // and if not first of each 4 characters,
	                // convert the first 8 bits to one ascii character
	                bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
	            ) {
	                // try to find character in table (0-63, not found => -1)
	                buffer = chars.indexOf(buffer);
	            }
	            return output;
	        });

	}());
	});

	// `Number.isNaN` method
	// https://tc39.es/ecma262/#sec-number.isnan
	_export({ target: 'Number', stat: true }, {
	  isNaN: function isNaN(number) {
	    // eslint-disable-next-line no-self-compare
	    return number != number;
	  }
	});

	var es_number_isNan = {

	};

	var isNan = path.Number.isNaN;

	var isNan$1 = isNan;

	var globalIsFinite = global_1.isFinite;

	// `Number.isFinite` method
	// https://tc39.es/ecma262/#sec-number.isfinite
	var numberIsFinite = Number.isFinite || function isFinite(it) {
	  return typeof it == 'number' && globalIsFinite(it);
	};

	// `Number.isFinite` method
	// https://tc39.es/ecma262/#sec-number.isfinite
	_export({ target: 'Number', stat: true }, { isFinite: numberIsFinite });

	var es_number_isFinite = {

	};

	var _isFinite = path.Number.isFinite;

	var _isFinite$1 = _isFinite;

	var MATCH = wellKnownSymbol('match');

	// `IsRegExp` abstract operation
	// https://tc39.es/ecma262/#sec-isregexp
	var isRegexp = function (it) {
	  var isRegExp;
	  return isObject(it) && ((isRegExp = it[MATCH]) !== undefined ? !!isRegExp : classofRaw(it) == 'RegExp');
	};

	var notARegexp = function (it) {
	  if (isRegexp(it)) {
	    throw TypeError("The method doesn't accept regular expressions");
	  } return it;
	};

	var MATCH$1 = wellKnownSymbol('match');

	var correctIsRegexpLogic = function (METHOD_NAME) {
	  var regexp = /./;
	  try {
	    '/./'[METHOD_NAME](regexp);
	  } catch (error1) {
	    try {
	      regexp[MATCH$1] = false;
	      return '/./'[METHOD_NAME](regexp);
	    } catch (error2) { /* empty */ }
	  } return false;
	};

	'use strict';





	// `String.prototype.includes` method
	// https://tc39.es/ecma262/#sec-string.prototype.includes
	_export({ target: 'String', proto: true, forced: !correctIsRegexpLogic('includes') }, {
	  includes: function includes(searchString /* , position = 0 */) {
	    return !!~String(requireObjectCoercible(this))
	      .indexOf(notARegexp(searchString), arguments.length > 1 ? arguments[1] : undefined);
	  }
	});

	var es_string_includes = {

	};

	var includes$2 = entryUnbind('String', 'includes');

	var includes$3 = includes$2;

	'use strict';

	var getOwnPropertyDescriptor$2 = objectGetOwnPropertyDescriptor.f;






	var nativeStartsWith = ''.startsWith;
	var min$2 = Math.min;

	var CORRECT_IS_REGEXP_LOGIC = correctIsRegexpLogic('startsWith');
	// https://github.com/zloirock/core-js/pull/702
	var MDN_POLYFILL_BUG = !isPure && !CORRECT_IS_REGEXP_LOGIC && !!function () {
	  var descriptor = getOwnPropertyDescriptor$2(String.prototype, 'startsWith');
	  return descriptor && !descriptor.writable;
	}();

	// `String.prototype.startsWith` method
	// https://tc39.es/ecma262/#sec-string.prototype.startswith
	_export({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG && !CORRECT_IS_REGEXP_LOGIC }, {
	  startsWith: function startsWith(searchString /* , position = 0 */) {
	    var that = String(requireObjectCoercible(this));
	    notARegexp(searchString);
	    var index = toLength(min$2(arguments.length > 1 ? arguments[1] : undefined, that.length));
	    var search = String(searchString);
	    return nativeStartsWith
	      ? nativeStartsWith.call(that, search, index)
	      : that.slice(index, index + search.length) === search;
	  }
	});

	var es_string_startsWith = {

	};

	var startsWith = entryUnbind('String', 'startsWith');

	var startsWith$1 = startsWith;

	(function() {

	    if (typeof Uint8Array !== 'undefined' || typeof window === 'undefined') {
	        return;
	    }

	    function subarray(start, end) {
	        return this.slice(start, end);
	    }

	    function set_(array, offset) {

	        if (arguments.length < 2) {
	            offset = 0;
	        }
	        for (var i = 0, n = array.length; i < n; ++i, ++offset) {
	            this[offset] = array[i] & 0xFF;
	        }
	    }

	    // we need typed arrays
	    function TypedArray(arg1) {

	        var result;
	        if (typeof arg1 === 'number') {
	            result = new Array(arg1);
	            for (var i = 0; i < arg1; ++i) {
	                result[i] = 0;
	            }
	        } else {
	            result = arg1.slice(0);
	        }
	        result.subarray = subarray;
	        result.buffer = result;
	        result.byteLength = result.length;
	        result.set = set_;
	        if (typeof arg1 === 'object' && arg1.buffer) {
	            result.buffer = arg1.buffer;
	        }

	        return result;
	    }

	    window.Uint8Array = TypedArray;
	    window.Uint32Array = TypedArray;
	    window.Int32Array = TypedArray;
	})();

	var props = {
	    x: 'x',
	    y: 'y',
	    width: 'w',
	    height: 'h',
	    minimum: 's',
	    maximum: 'l',
	    diagonal: 'd'
	};
	var propsList = Object.keys(props).map(function (key) { return props[key]; }).join('');
	var numberPattern = '[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?';
	var findSpacesRegex = /\s/g;
	var parseExpressionRegExp = new RegExp(("^(" + numberPattern + "\\*)?([" + propsList + "])(/" + numberPattern + ")?([-+]{1,2}" + numberPattern + ")?$"), 'g');

	function throwInvalid(expression) {
	    throw new Error(("Invalid calc() expression: " + expression));
	}

	function evalCalcExpression(expression, bbox) {
	    var match = parseExpressionRegExp.exec(expression.replace(findSpacesRegex, ''));
	    if (!match) { throwInvalid(expression); }
	    parseExpressionRegExp.lastIndex = 0; // reset regex results for the next run
	    var multiply = match[1];
	    var property = match[2];
	    var divide = match[3];
	    var add = match[4];
	    var x = bbox.x;
	    var y = bbox.y;
	    var width = bbox.width;
	    var height = bbox.height;
	    var value = 0;
	    switch (property) {
	        case props.width: {
	            value = width;
	            break;
	        }
	        case props.height: {
	            value = height;
	            break;
	        }
	        case props.x: {
	            value = x;
	            break;
	        }
	        case props.y: {
	            value = y;
	            break;
	        }
	        case props.minimum: {
	            value = Math.min(height, width);
	            break;
	        }
	        case props.maximum: {
	            value = Math.max(height, width);
	            break;
	        }
	        case props.diagonal: {
	            value = Math.sqrt((height * height) + (width * width));
	            break;
	        }
	    }
	    if (multiply) {
	        // e.g "2*"
	        value *= parseFloat(multiply);
	    }
	    if (divide) {
	        // e.g "/2"
	        value /= parseFloat(divide.slice(1));
	    }
	    if (add) {
	        value += evalAddExpression(add);
	    }
	    return value;
	}

	function evalAddExpression(addExpression) {
	    if (!addExpression) { return 0; }
	    var sign = addExpression[0];
	    switch (sign) {
	        case '+': {
	            return parseFloat(addExpression.substr(1));
	        }
	        case '-': {
	            return -parseFloat(addExpression.substr(1));
	        }
	    }
	    return parseFloat(addExpression);
	}

	function isCalcAttribute(value) {
	    return typeof value === 'string' && value.includes('calc');
	}

	var calcStart = 'calc(';
	var calcStartOffset = calcStart.length;

	function evalCalcAttribute(attributeValue, refBBox) {
	    var value = attributeValue;
	    var startSearchIndex = 0;
	    do {
	        var calcIndex = value.indexOf(calcStart, startSearchIndex);
	        if (calcIndex === -1) { return value; }
	        var calcEndIndex = calcIndex + calcStartOffset;
	        var brackets = 1;
	        findClosingBracket: do {
	            switch (value[calcEndIndex]) {
	                case '(': {
	                    brackets++;
	                    break;
	                }
	                case ')': {
	                    brackets--;
	                    if (brackets === 0) { break findClosingBracket; }
	                    break;
	                }
	                case undefined: {
	                    // Could not find the closing bracket.
	                    throwInvalid(value);
	                }
	            }
	            calcEndIndex++;
	        } while (true);
	        // Get the calc() expression without nested calcs (recursion)
	        var expression = value.slice(calcIndex + calcStartOffset, calcEndIndex);
	        if (isCalcAttribute(expression)) {
	            expression = evalCalcAttribute(expression, refBBox);
	        }
	        // Eval the calc() expression without nested calcs.
	        var calcValue = String(evalCalcExpression(expression, refBBox));
	        // Replace the calc() expression and continue search
	        value = value.slice(0, calcIndex) + calcValue + value.slice(calcEndIndex + 1);
	        startSearchIndex = calcIndex + calcValue.length;
	    } while (true);
	}

	// Declare shorthands to the most used math functions.
	var round = Math.round;
	var floor$1 = Math.floor;
	var PI = Math.PI;

	var scale = {

	    // Return the `value` from the `domain` interval scaled to the `range` interval.
	    linear: function(domain, range, value) {

	        var domainSpan = domain[1] - domain[0];
	        var rangeSpan = range[1] - range[0];
	        return (((value - domain[0]) / domainSpan) * rangeSpan + range[0]) || 0;
	    }
	};

	var normalizeAngle = function(angle) {

	    return (angle % 360) + (angle < 0 ? 360 : 0);
	};

	var snapToGrid = function(value, gridSize) {

	    return gridSize * round(value / gridSize);
	};

	var toDeg = function(rad) {

	    return (180 * rad / PI) % 360;
	};

	var toRad = function(deg, over360) {

	    over360 = over360 || false;
	    deg = over360 ? deg : (deg % 360);
	    return deg * PI / 180;
	};

	// Return a random integer from the interval [min,max], inclusive.
	var random = function(min, max) {

	    if (max === undefined) {
	        // use first argument as max, min is 0
	        max = (min === undefined) ? 1 : min;
	        min = 0;

	    } else if (max < min) {
	        // switch max and min
	        var temp = min;
	        min = max;
	        max = temp;
	    }

	    return floor$1((Math.random() * (max - min + 1)) + min);
	};

	// @return the bearing (cardinal direction) of the line. For example N, W, or SE.

	var cos = Math.cos;
	var sin = Math.sin;
	var atan2 = Math.atan2;

	var bearing = function(p, q) {

	    var lat1 = toRad(p.y);
	    var lat2 = toRad(q.y);
	    var lon1 = p.x;
	    var lon2 = q.x;
	    var dLon = toRad(lon2 - lon1);
	    var y = sin(dLon) * cos(lat2);
	    var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
	    var brng = toDeg(atan2(y, x));

	    var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N'];

	    var index = brng - 22.5;
	    if (index < 0)
	        { index += 360; }
	    index = parseInt(index / 45);

	    return bearings[index];
	};

	// @return {integer} length without sqrt
	// @note for applications where the exact length is not necessary (e.g. compare only)
	var squaredLength = function(start, end) {

	    var x0 = start.x;
	    var y0 = start.y;
	    var x1 = end.x;
	    var y1 = end.y;
	    return (x0 -= x1) * x0 + (y0 -= y1) * y0;
	};

	var length = function(start, end) {
	    return Math.sqrt(squaredLength(start, end));
	};

	var types = {
	    Point: 1,
	    Line: 2,
	    Ellipse: 3,
	    Rect: 4,
	    Polyline: 5,
	    Polygon: 6,
	    Curve: 7,
	    Path: 8
	};

	/*
	    Point is the most basic object consisting of x/y coordinate.

	    Possible instantiations are:
	    * `Point(10, 20)`
	    * `new Point(10, 20)`
	    * `Point('10 20')`
	    * `Point(Point(10, 20))`
	*/

	var abs = Math.abs;
	var cos$1 = Math.cos;
	var sin$1 = Math.sin;
	var sqrt = Math.sqrt;
	var min$3 = Math.min;
	var max$1 = Math.max;
	var atan2$1 = Math.atan2;
	var round$1 = Math.round;
	var pow = Math.pow;
	var PI$1 = Math.PI;

	var Point = function(x, y) {

	    if (!(this instanceof Point)) {
	        return new Point(x, y);
	    }

	    if (typeof x === 'string') {
	        var xy = x.split(x.indexOf('@') === -1 ? ' ' : '@');
	        x = parseFloat(xy[0]);
	        y = parseFloat(xy[1]);

	    } else if (Object(x) === x) {
	        y = x.y;
	        x = x.x;
	    }

	    this.x = x === undefined ? 0 : x;
	    this.y = y === undefined ? 0 : y;
	};

	// Alternative constructor, from polar coordinates.
	// @param {number} Distance.
	// @param {number} Angle in radians.
	// @param {point} [optional] Origin.
	Point.fromPolar = function(distance, angle, origin) {

	    origin = new Point(origin);
	    var x = abs(distance * cos$1(angle));
	    var y = abs(distance * sin$1(angle));
	    var deg = normalizeAngle(toDeg(angle));

	    if (deg < 90) {
	        y = -y;

	    } else if (deg < 180) {
	        x = -x;
	        y = -y;

	    } else if (deg < 270) {
	        x = -x;
	    }

	    return new Point(origin.x + x, origin.y + y);
	};

	// Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`.
	Point.random = function(x1, x2, y1, y2) {

	    return new Point(random(x1, x2), random(y1, y2));
	};

	Point.prototype = {

	    type: types.Point,

	    chooseClosest: function(points) {

	        var n = points.length;
	        if (n === 1) { return new Point(points[0]); }
	        var closest = null;
	        var minSqrDistance = Infinity;
	        for (var i = 0; i < n; i++) {
	            var p = new Point(points[i]);
	            var sqrDistance = this.squaredDistance(p);
	            if (sqrDistance < minSqrDistance) {
	                closest = p;
	                minSqrDistance = sqrDistance;
	            }
	        }
	        return closest;
	    },

	    // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`,
	    // otherwise return point itself.
	    // (see Squeak Smalltalk, Point>>adhereTo:)
	    adhereToRect: function(r) {

	        if (r.containsPoint(this)) {
	            return this;
	        }

	        this.x = min$3(max$1(this.x, r.x), r.x + r.width);
	        this.y = min$3(max$1(this.y, r.y), r.y + r.height);
	        return this;
	    },

	    // Compute the angle between vector from me to p1 and the vector from me to p2.
	    // ordering of points p1 and p2 is important!
	    // theta function's angle convention:
	    // returns angles between 0 and 180 when the angle is counterclockwise
	    // returns angles between 180 and 360 to convert clockwise angles into counterclockwise ones
	    // returns NaN if any of the points p1, p2 is coincident with this point
	    angleBetween: function(p1, p2) {

	        var angleBetween = (this.equals(p1) || this.equals(p2)) ? NaN : (this.theta(p2) - this.theta(p1));

	        if (angleBetween < 0) {
	            angleBetween += 360; // correction to keep angleBetween between 0 and 360
	        }

	        return angleBetween;
	    },

	    // Return the bearing between me and the given point.
	    bearing: function(point) {
	        return bearing(this, point);
	    },

	    // Returns change in angle from my previous position (-dx, -dy) to my new position
	    // relative to ref point.
	    changeInAngle: function(dx, dy, ref) {

	        // Revert the translation and measure the change in angle around x-axis.
	        return this.clone().offset(-dx, -dy).theta(ref) - this.theta(ref);
	    },

	    clone: function() {

	        return new Point(this);
	    },

	    // Returns the cross product of this point relative to two other points
	    // this point is the common point
	    // point p1 lies on the first vector, point p2 lies on the second vector
	    // watch out for the ordering of points p1 and p2!
	    // positive result indicates a clockwise ("right") turn from first to second vector
	    // negative result indicates a counterclockwise ("left") turn from first to second vector
	    // zero indicates that the first and second vector are collinear
	    // note that the above directions are reversed from the usual answer on the Internet
	    // that is because we are in a left-handed coord system (because the y-axis points downward)
	    cross: function(p1, p2) {

	        return (p1 && p2) ? (((p2.x - this.x) * (p1.y - this.y)) - ((p2.y - this.y) * (p1.x - this.x))) : NaN;
	    },

	    difference: function(dx, dy) {

	        if ((Object(dx) === dx)) {
	            dy = dx.y;
	            dx = dx.x;
	        }

	        return new Point(this.x - (dx || 0), this.y - (dy || 0));
	    },

	    // Returns distance between me and point `p`.
	    distance: function(p) {
	        return length(this, p);
	    },

	    // Returns the dot product of this point with given other point
	    dot: function(p) {

	        return p ? (this.x * p.x + this.y * p.y) : NaN;
	    },

	    equals: function(p) {

	        return !!p &&
	            this.x === p.x &&
	            this.y === p.y;
	    },

	    // Linear interpolation
	    lerp: function(p, t) {

	        var x = this.x;
	        var y = this.y;
	        return new Point((1 - t) * x + t * p.x, (1 - t) * y + t * p.y);
	    },

	    magnitude: function() {

	        return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01;
	    },

	    // Returns a manhattan (taxi-cab) distance between me and point `p`.
	    manhattanDistance: function(p) {

	        return abs(p.x - this.x) + abs(p.y - this.y);
	    },

	    // Move point on line starting from ref ending at me by
	    // distance distance.
	    move: function(ref, distance) {

	        var theta = toRad((new Point(ref)).theta(this));
	        var offset = this.offset(cos$1(theta) * distance, -sin$1(theta) * distance);
	        return offset;
	    },

	    // Scales x and y such that the distance between the point and the origin (0,0) is equal to the given length.
	    normalize: function(length) {

	        var scale = (length || 1) / this.magnitude();
	        return this.scale(scale, scale);
	    },

	    // Offset me by the specified amount.
	    offset: function(dx, dy) {

	        if ((Object(dx) === dx)) {
	            dy = dx.y;
	            dx = dx.x;
	        }

	        this.x += dx || 0;
	        this.y += dy || 0;
	        return this;
	    },

	    // Returns a point that is the reflection of me with
	    // the center of inversion in ref point.
	    reflection: function(ref) {

	        return (new Point(ref)).move(this, this.distance(ref));
	    },

	    // Rotate point by angle around origin.
	    // Angle is flipped because this is a left-handed coord system (y-axis points downward).
	    rotate: function(origin, angle) {

	        if (angle === 0) { return this; }

	        origin = origin || new Point(0, 0);

	        angle = toRad(normalizeAngle(-angle));
	        var cosAngle = cos$1(angle);
	        var sinAngle = sin$1(angle);

	        var x = (cosAngle * (this.x - origin.x)) - (sinAngle * (this.y - origin.y)) + origin.x;
	        var y = (sinAngle * (this.x - origin.x)) + (cosAngle * (this.y - origin.y)) + origin.y;

	        this.x = x;
	        this.y = y;
	        return this;
	    },

	    round: function(precision) {

	        var f = 1; // case 0
	        if (precision) {
	            switch (precision) {
	                case 1: f = 10; break;
	                case 2: f = 100; break;
	                case 3: f = 1000; break;
	                default: f = pow(10, precision); break;
	            }
	        }

	        this.x = round$1(this.x * f) / f;
	        this.y = round$1(this.y * f) / f;
	        return this;
	    },

	    // Scale point with origin.
	    scale: function(sx, sy, origin) {

	        origin = (origin && new Point(origin)) || new Point(0, 0);
	        this.x = origin.x + sx * (this.x - origin.x);
	        this.y = origin.y + sy * (this.y - origin.y);
	        return this;
	    },

	    snapToGrid: function(gx, gy) {

	        this.x = snapToGrid(this.x, gx);
	        this.y = snapToGrid(this.y, gy || gx);
	        return this;
	    },

	    squaredDistance: function(p) {
	        return squaredLength(this, p);
	    },

	    // Compute the angle between me and `p` and the x axis.
	    // (cartesian-to-polar coordinates conversion)
	    // Return theta angle in degrees.
	    theta: function(p) {

	        p = new Point(p);

	        // Invert the y-axis.
	        var y = -(p.y - this.y);
	        var x = p.x - this.x;
	        var rad = atan2$1(y, x); // defined for all 0 corner cases

	        // Correction for III. and IV. quadrant.
	        if (rad < 0) {
	            rad = 2 * PI$1 + rad;
	        }

	        return 180 * rad / PI$1;
	    },

	    toJSON: function() {

	        return { x: this.x, y: this.y };
	    },

	    // Converts rectangular to polar coordinates.
	    // An origin can be specified, otherwise it's 0@0.
	    toPolar: function(o) {

	        o = (o && new Point(o)) || new Point(0, 0);
	        var x = this.x;
	        var y = this.y;
	        this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r
	        this.y = toRad(o.theta(new Point(x, y)));
	        return this;
	    },

	    toString: function() {

	        return this.x + '@' + this.y;
	    },

	    serialize: function() {

	        return this.x + ',' + this.y;
	    },

	    update: function(x, y) {

	        if ((Object(x) === x)) {
	            y = x.y;
	            x = x.x;
	        }

	        this.x = x || 0;
	        this.y = y || 0;
	        return this;
	    },

	    // Compute the angle between the vector from 0,0 to me and the vector from 0,0 to p.
	    // Returns NaN if p is at 0,0.
	    vectorAngle: function(p) {

	        var zero = new Point(0, 0);
	        return zero.angleBetween(this, p);
	    }
	};

	Point.prototype.translate = Point.prototype.offset;

	// For backwards compatibility:
	var point = Point;

	var max$2 = Math.max;
	var min$4 = Math.min;

	var Line = function(p1, p2) {

	    if (!(this instanceof Line)) {
	        return new Line(p1, p2);
	    }

	    if (p1 instanceof Line) {
	        return new Line(p1.start, p1.end);
	    }

	    this.start = new Point(p1);
	    this.end = new Point(p2);
	};

	Line.prototype = {

	    type: types.Line,

	    // @returns the angle of incline of the line.
	    angle: function() {

	        var horizontalPoint = new Point(this.start.x + 1, this.start.y);
	        return this.start.angleBetween(this.end, horizontalPoint);
	    },

	    bbox: function() {

	        var left = min$4(this.start.x, this.end.x);
	        var top = min$4(this.start.y, this.end.y);
	        var right = max$2(this.start.x, this.end.x);
	        var bottom = max$2(this.start.y, this.end.y);

	        return new Rect(left, top, (right - left), (bottom - top));
	    },

	    // @return the bearing (cardinal direction) of the line. For example N, W, or SE.
	    // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N.
	    bearing: function() {
	        return bearing(this.start, this.end);
	    },

	    clone: function() {

	        return new Line(this.start, this.end);
	    },

	    // @return {point} the closest point on the line to point `p`
	    closestPoint: function(p) {

	        return this.pointAt(this.closestPointNormalizedLength(p));
	    },

	    closestPointLength: function(p) {

	        return this.closestPointNormalizedLength(p) * this.length();
	    },

	    // @return {number} the normalized length of the closest point on the line to point `p`
	    closestPointNormalizedLength: function(p) {

	        var product = this.vector().dot((new Line(this.start, p)).vector());
	        var cpNormalizedLength = min$4(1, max$2(0, product / this.squaredLength()));

	        // cpNormalizedLength returns `NaN` if this line has zero length
	        // we can work with that - if `NaN`, return 0
	        if (cpNormalizedLength !== cpNormalizedLength) { return 0; } // condition evaluates to `true` if and only if cpNormalizedLength is `NaN`
	        // (`NaN` is the only value that is not equal to itself)

	        return cpNormalizedLength;
	    },

	    closestPointTangent: function(p) {

	        return this.tangentAt(this.closestPointNormalizedLength(p));
	    },

	    // Returns `true` if the point lies on the line.
	    containsPoint: function(p) {

	        var start = this.start;
	        var end = this.end;

	        if (start.cross(p, end) !== 0) { return false; }
	        // else: cross product of 0 indicates that this line and the vector to `p` are collinear

	        var length = this.length();
	        if ((new Line(start, p)).length() > length) { return false; }
	        if ((new Line(p, end)).length() > length) { return false; }
	        // else: `p` lies between start and end of the line

	        return true;
	    },

	    // Divides the line into two at requested `ratio` between 0 and 1.
	    divideAt: function(ratio) {

	        var dividerPoint = this.pointAt(ratio);

	        // return array with two lines
	        return [
	            new Line(this.start, dividerPoint),
	            new Line(dividerPoint, this.end)
	        ];
	    },

	    // Divides the line into two at requested `length`.
	    divideAtLength: function(length) {

	        var dividerPoint = this.pointAtLength(length);

	        // return array with two new lines
	        return [
	            new Line(this.start, dividerPoint),
	            new Line(dividerPoint, this.end)
	        ];
	    },

	    equals: function(l) {

	        return !!l &&
	            this.start.x === l.start.x &&
	            this.start.y === l.start.y &&
	            this.end.x === l.end.x &&
	            this.end.y === l.end.y;
	    },

	    // @return {point} Point where I'm intersecting a line.
	    // @return [point] Points where I'm intersecting a rectangle.
	    // @see Squeak Smalltalk, LineSegment>>intersectionWith:
	    intersect: function(shape, opt) {

	        if (shape && shape.intersectionWithLine) {
	            var intersection = shape.intersectionWithLine(this, opt);

	            // Backwards compatibility
	            if (intersection && (shape instanceof Line)) {
	                intersection = intersection[0];
	            }

	            return intersection;
	        }

	        return null;
	    },

	    intersectionWithLine: function(line) {

	        var pt1Dir = new Point(this.end.x - this.start.x, this.end.y - this.start.y);
	        var pt2Dir = new Point(line.end.x - line.start.x, line.end.y - line.start.y);
	        var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x);
	        var deltaPt = new Point(line.start.x - this.start.x, line.start.y - this.start.y);
	        var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x);
	        var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x);

	        if (det === 0 || alpha * det < 0 || beta * det < 0) {
	            // No intersection found.
	            return null;
	        }

	        if (det > 0) {
	            if (alpha > det || beta > det) {
	                return null;
	            }

	        } else {
	            if (alpha < det || beta < det) {
	                return null;
	            }
	        }

	        return [new Point(
	            this.start.x + (alpha * pt1Dir.x / det),
	            this.start.y + (alpha * pt1Dir.y / det)
	        )];
	    },

	    isDifferentiable: function() {

	        return !this.start.equals(this.end);
	    },

	    // @return {double} length of the line
	    length: function() {
	        return length(this.start, this.end);
	    },

	    // @return {point} my midpoint
	    midpoint: function() {

	        return new Point(
	            (this.start.x + this.end.x) / 2,
	            (this.start.y + this.end.y) / 2
	        );
	    },

	    parallel: function(distance) {
	        var l = this.clone();
	        if (!this.isDifferentiable()) { return l; }
	        var start = l.start;
	        var end = l.end;
	        var eRef = start.clone().rotate(end, 270);
	        var sRef = end.clone().rotate(start, 90);
	        start.move(sRef, distance);
	        end.move(eRef, distance);
	        return l;
	    },

	    // @return {point} my point at 't' <0,1>
	    pointAt: function(t) {

	        var start = this.start;
	        var end = this.end;

	        if (t <= 0) { return start.clone(); }
	        if (t >= 1) { return end.clone(); }

	        return start.lerp(end, t);
	    },

	    pointAtLength: function(length) {

	        var start = this.start;
	        var end = this.end;

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        var lineLength = this.length();
	        if (length >= lineLength) { return (fromStart ? end.clone() : start.clone()); }

	        return this.pointAt((fromStart ? (length) : (lineLength - length)) / lineLength);
	    },

	    // @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line.
	    pointOffset: function(p) {

	        // Find the sign of the determinant of vectors (start,end), where p is the query point.
	        p = new Point(p);
	        var start = this.start;
	        var end = this.end;
	        var determinant = ((end.x - start.x) * (p.y - start.y) - (end.y - start.y) * (p.x - start.x));

	        return determinant / this.length();
	    },

	    rotate: function(origin, angle) {

	        this.start.rotate(origin, angle);
	        this.end.rotate(origin, angle);
	        return this;
	    },

	    round: function(precision) {

	        this.start.round(precision);
	        this.end.round(precision);
	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        this.start.scale(sx, sy, origin);
	        this.end.scale(sx, sy, origin);
	        return this;
	    },

	    // @return {number} scale the line so that it has the requested length
	    setLength: function(length) {

	        var currentLength = this.length();
	        if (!currentLength) { return this; }

	        var scaleFactor = length / currentLength;
	        return this.scale(scaleFactor, scaleFactor, this.start);
	    },

	    // @return {integer} length without sqrt
	    // @note for applications where the exact length is not necessary (e.g. compare only)
	    squaredLength: function() {
	        return squaredLength(this.start, this.end);
	    },

	    tangentAt: function(t) {

	        if (!this.isDifferentiable()) { return null; }

	        var start = this.start;
	        var end = this.end;

	        var tangentStart = this.pointAt(t); // constrains `t` between 0 and 1

	        var tangentLine = new Line(start, end);
	        tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y); // move so that tangent line starts at the point requested

	        return tangentLine;
	    },

	    tangentAtLength: function(length) {

	        if (!this.isDifferentiable()) { return null; }

	        var start = this.start;
	        var end = this.end;

	        var tangentStart = this.pointAtLength(length);

	        var tangentLine = new Line(start, end);
	        tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y); // move so that tangent line starts at the point requested

	        return tangentLine;
	    },

	    toString: function() {

	        return this.start.toString() + ' ' + this.end.toString();
	    },

	    serialize: function() {

	        return this.start.serialize() + ' ' + this.end.serialize();
	    },

	    translate: function(tx, ty) {

	        this.start.translate(tx, ty);
	        this.end.translate(tx, ty);
	        return this;
	    },

	    // @return vector {point} of the line
	    vector: function() {

	        return new Point(this.end.x - this.start.x, this.end.y - this.start.y);
	    }
	};

	// For backwards compatibility:
	Line.prototype.intersection = Line.prototype.intersect;


	// For backwards compatibility:
	var line = Line;

	var sqrt$1 = Math.sqrt;
	var round$2 = Math.round;
	var pow$1 = Math.pow;

	var Ellipse = function(c, a, b) {

	    if (!(this instanceof Ellipse)) {
	        return new Ellipse(c, a, b);
	    }

	    if (c instanceof Ellipse) {
	        return new Ellipse(new Point(c.x, c.y), c.a, c.b);
	    }

	    c = new Point(c);
	    this.x = c.x;
	    this.y = c.y;
	    this.a = a;
	    this.b = b;
	};

	Ellipse.fromRect = function(rect) {

	    rect = new Rect(rect);
	    return new Ellipse(rect.center(), rect.width / 2, rect.height / 2);
	};

	Ellipse.prototype = {

	    type: types.Ellipse,

	    bbox: function() {

	        return new Rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b);
	    },

	    /**
	     * @returns {g.Point}
	     */
	    center: function() {

	        return new Point(this.x, this.y);
	    },

	    clone: function() {

	        return new Ellipse(this);
	    },

	    /**
	     * @param {g.Point} p
	     * @returns {boolean}
	     */
	    containsPoint: function(p) {

	        return this.normalizedDistance(p) <= 1;
	    },

	    equals: function(ellipse) {

	        return !!ellipse &&
	            ellipse.x === this.x &&
	            ellipse.y === this.y &&
	            ellipse.a === this.a &&
	            ellipse.b === this.b;
	    },

	    // inflate by dx and dy
	    // @param dx {delta_x} representing additional size to x
	    // @param dy {delta_y} representing additional size to y -
	    // dy param is not required -> in that case y is sized by dx
	    inflate: function(dx, dy) {
	        if (dx === undefined) {
	            dx = 0;
	        }

	        if (dy === undefined) {
	            dy = dx;
	        }

	        this.a += 2 * dx;
	        this.b += 2 * dy;

	        return this;
	    },

	    intersectionWithLine: function(line) {

	        var intersections = [];
	        var a1 = line.start;
	        var a2 = line.end;
	        var rx = this.a;
	        var ry = this.b;
	        var dir = line.vector();
	        var diff = a1.difference(new Point(this));
	        var mDir = new Point(dir.x / (rx * rx), dir.y / (ry * ry));
	        var mDiff = new Point(diff.x / (rx * rx), diff.y / (ry * ry));

	        var a = dir.dot(mDir);
	        var b = dir.dot(mDiff);
	        var c = diff.dot(mDiff) - 1.0;
	        var d = b * b - a * c;

	        if (d < 0) {
	            return null;
	        } else if (d > 0) {
	            var root = sqrt$1(d);
	            var ta = (-b - root) / a;
	            var tb = (-b + root) / a;

	            if ((ta < 0 || 1 < ta) && (tb < 0 || 1 < tb)) {
	                // if ((ta < 0 && tb < 0) || (ta > 1 && tb > 1)) outside else inside
	                return null;
	            } else {
	                if (0 <= ta && ta <= 1) { intersections.push(a1.lerp(a2, ta)); }
	                if (0 <= tb && tb <= 1) { intersections.push(a1.lerp(a2, tb)); }
	            }
	        } else {
	            var t = -b / a;
	            if (0 <= t && t <= 1) {
	                intersections.push(a1.lerp(a2, t));
	            } else {
	                // outside
	                return null;
	            }
	        }

	        return intersections;
	    },

	    // Find point on me where line from my center to
	    // point p intersects my boundary.
	    // @param {number} angle If angle is specified, intersection with rotated ellipse is computed.
	    intersectionWithLineFromCenterToPoint: function(p, angle) {

	        p = new Point(p);

	        if (angle) { p.rotate(new Point(this.x, this.y), angle); }

	        var dx = p.x - this.x;
	        var dy = p.y - this.y;
	        var result;

	        if (dx === 0) {
	            result = this.bbox().pointNearestToPoint(p);
	            if (angle) { return result.rotate(new Point(this.x, this.y), -angle); }
	            return result;
	        }

	        var m = dy / dx;
	        var mSquared = m * m;
	        var aSquared = this.a * this.a;
	        var bSquared = this.b * this.b;

	        var x = sqrt$1(1 / ((1 / aSquared) + (mSquared / bSquared)));
	        x = dx < 0 ? -x : x;

	        var y = m * x;
	        result = new Point(this.x + x, this.y + y);

	        if (angle) { return result.rotate(new Point(this.x, this.y), -angle); }
	        return result;
	    },

	    /**
	     * @param {g.Point} point
	     * @returns {number} result < 1 - inside ellipse, result == 1 - on ellipse boundary, result > 1 - outside
	     */
	    normalizedDistance: function(point) {

	        var x0 = point.x;
	        var y0 = point.y;
	        var a = this.a;
	        var b = this.b;
	        var x = this.x;
	        var y = this.y;

	        return ((x0 - x) * (x0 - x)) / (a * a) + ((y0 - y) * (y0 - y)) / (b * b);
	    },

	    round: function(precision) {

	        var f = 1; // case 0
	        if (precision) {
	            switch (precision) {
	                case 1: f = 10; break;
	                case 2: f = 100; break;
	                case 3: f = 1000; break;
	                default: f = pow$1(10, precision); break;
	            }
	        }

	        this.x = round$2(this.x * f) / f;
	        this.y = round$2(this.y * f) / f;
	        this.a = round$2(this.a * f) / f;
	        this.b = round$2(this.b * f) / f;
	        return this;
	    },

	    /** Compute angle between tangent and x axis
	     * @param {g.Point} p Point of tangency, it has to be on ellipse boundaries.
	     * @returns {number} angle between tangent and x axis
	     */
	    tangentTheta: function(p) {

	        var refPointDelta = 30;
	        var x0 = p.x;
	        var y0 = p.y;
	        var a = this.a;
	        var b = this.b;
	        var center = this.bbox().center();
	        var m = center.x;
	        var n = center.y;

	        var q1 = x0 > center.x + a / 2;
	        var q3 = x0 < center.x - a / 2;

	        var y, x;
	        if (q1 || q3) {
	            y = x0 > center.x ? y0 - refPointDelta : y0 + refPointDelta;
	            x = (a * a / (x0 - m)) - (a * a * (y0 - n) * (y - n)) / (b * b * (x0 - m)) + m;

	        } else {
	            x = y0 > center.y ? x0 + refPointDelta : x0 - refPointDelta;
	            y = (b * b / (y0 - n)) - (b * b * (x0 - m) * (x - m)) / (a * a * (y0 - n)) + n;
	        }

	        return (new Point(x, y)).theta(p);

	    },

	    toString: function() {

	        return (new Point(this.x, this.y)).toString() + ' ' + this.a + ' ' + this.b;
	    }
	};

	// For backwards compatibility:
	var ellipse = Ellipse;

	var abs$1 = Math.abs;
	var cos$2 = Math.cos;
	var sin$2 = Math.sin;
	var min$5 = Math.min;
	var max$3 = Math.max;
	var round$3 = Math.round;
	var pow$2 = Math.pow;

	var Rect = function(x, y, w, h) {

	    if (!(this instanceof Rect)) {
	        return new Rect(x, y, w, h);
	    }

	    if ((Object(x) === x)) {
	        y = x.y;
	        w = x.width;
	        h = x.height;
	        x = x.x;
	    }

	    this.x = x === undefined ? 0 : x;
	    this.y = y === undefined ? 0 : y;
	    this.width = w === undefined ? 0 : w;
	    this.height = h === undefined ? 0 : h;
	};

	Rect.fromEllipse = function(e) {

	    e = new Ellipse(e);
	    return new Rect(e.x - e.a, e.y - e.b, 2 * e.a, 2 * e.b);
	};

	Rect.fromPointUnion = function() {
	    var points = [], len = arguments.length;
	    while ( len-- ) points[ len ] = arguments[ len ];


	    if (points.length === 0) { return null; }

	    var p = new Point();
	    var minX, minY, maxX, maxY;
	    minX = minY = Infinity;
	    maxX = maxY = -Infinity;

	    for (var i = 0; i < points.length; i++) {
	        p.update(points[i]);
	        var x = p.x;
	        var y = p.y;

	        if (x < minX) { minX = x; }
	        if (x > maxX) { maxX = x; }
	        if (y < minY) { minY = y; }
	        if (y > maxY) { maxY = y; }
	    }

	    return new Rect(minX, minY, maxX - minX, maxY - minY);
	};

	Rect.fromRectUnion = function() {
	    var rects = [], len = arguments.length;
	    while ( len-- ) rects[ len ] = arguments[ len ];


	    if (rects.length === 0) { return null; }

	    var r = new Rect();
	    var minX, minY, maxX, maxY;
	    minX = minY = Infinity;
	    maxX = maxY = -Infinity;

	    for (var i = 0; i < rects.length; i++) {
	        r.update(rects[i]);
	        var x = r.x;
	        var y = r.y;
	        var mX = x + r.width;
	        var mY = y + r.height;

	        if (x < minX) { minX = x; }
	        if (mX > maxX) { maxX = mX; }
	        if (y < minY) { minY = y; }
	        if (mY > maxY) { maxY = mY; }
	    }

	    return new Rect(minX, minY, maxX - minX, maxY - minY);
	};

	Rect.prototype = {

	    type: types.Rect,

	    // Find my bounding box when I'm rotated with the center of rotation in the center of me.
	    // @return r {rectangle} representing a bounding box
	    bbox: function(angle) {
	        return this.clone().rotateAroundCenter(angle);
	    },

	    rotateAroundCenter: function(angle) {
	        if (!angle) { return this; }
	        var ref = this;
	        var width = ref.width;
	        var height = ref.height;
	        var theta = toRad(angle);
	        var st = abs$1(sin$2(theta));
	        var ct = abs$1(cos$2(theta));
	        var w = width * ct + height * st;
	        var h = width * st + height * ct;
	        this.x += (width - w) / 2;
	        this.y += (height - h) / 2;
	        this.width = w;
	        this.height = h;
	        return this;
	    },

	    bottomLeft: function() {

	        return new Point(this.x, this.y + this.height);
	    },

	    bottomLine: function() {

	        return new Line(this.bottomLeft(), this.bottomRight());
	    },

	    bottomMiddle: function() {

	        return new Point(this.x + this.width / 2, this.y + this.height);
	    },

	    center: function() {

	        return new Point(this.x + this.width / 2, this.y + this.height / 2);
	    },

	    clone: function() {

	        return new Rect(this);
	    },

	    // @return {bool} true if point p is inside me.
	    containsPoint: function(p) {
	        p = new Point(p);
	        return p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height;
	    },

	    // @return {bool} true if rectangle `r` is inside me.
	    containsRect: function(r) {

	        var r0 = new Rect(this).normalize();
	        var r1 = new Rect(r).normalize();
	        var w0 = r0.width;
	        var h0 = r0.height;
	        var w1 = r1.width;
	        var h1 = r1.height;

	        if (!w0 || !h0 || !w1 || !h1) {
	            // At least one of the dimensions is 0
	            return false;
	        }

	        var x0 = r0.x;
	        var y0 = r0.y;
	        var x1 = r1.x;
	        var y1 = r1.y;

	        w1 += x1;
	        w0 += x0;
	        h1 += y1;
	        h0 += y0;

	        return x0 <= x1 && w1 <= w0 && y0 <= y1 && h1 <= h0;
	    },

	    corner: function() {

	        return new Point(this.x + this.width, this.y + this.height);
	    },

	    // @return {boolean} true if rectangles are equal.
	    equals: function(r) {

	        var mr = (new Rect(this)).normalize();
	        var nr = (new Rect(r)).normalize();
	        return mr.x === nr.x && mr.y === nr.y && mr.width === nr.width && mr.height === nr.height;
	    },

	    // inflate by dx and dy, recompute origin [x, y]
	    // @param dx {delta_x} representing additional size to x
	    // @param dy {delta_y} representing additional size to y -
	    // dy param is not required -> in that case y is sized by dx
	    inflate: function(dx, dy) {

	        if (dx === undefined) {
	            dx = 0;
	        }

	        if (dy === undefined) {
	            dy = dx;
	        }

	        this.x -= dx;
	        this.y -= dy;
	        this.width += 2 * dx;
	        this.height += 2 * dy;

	        return this;
	    },

	    // @return {rect} if rectangles intersect, {null} if not.
	    intersect: function(r) {

	        var myOrigin = this.origin();
	        var myCorner = this.corner();
	        var rOrigin = r.origin();
	        var rCorner = r.corner();

	        // No intersection found
	        if (rCorner.x <= myOrigin.x ||
	            rCorner.y <= myOrigin.y ||
	            rOrigin.x >= myCorner.x ||
	            rOrigin.y >= myCorner.y) { return null; }

	        var x = max$3(myOrigin.x, rOrigin.x);
	        var y = max$3(myOrigin.y, rOrigin.y);

	        return new Rect(x, y, min$5(myCorner.x, rCorner.x) - x, min$5(myCorner.y, rCorner.y) - y);
	    },

	    intersectionWithLine: function(line) {

	        var r = this;
	        var rectLines = [r.topLine(), r.rightLine(), r.bottomLine(), r.leftLine()];
	        var points = [];
	        var dedupeArr = [];
	        var pt, i;

	        var n = rectLines.length;
	        for (i = 0; i < n; i++) {

	            pt = line.intersect(rectLines[i]);
	            if (pt !== null && dedupeArr.indexOf(pt.toString()) < 0) {
	                points.push(pt);
	                dedupeArr.push(pt.toString());
	            }
	        }

	        return points.length > 0 ? points : null;
	    },

	    // Find point on my boundary where line starting
	    // from my center ending in point p intersects me.
	    // @param {number} angle If angle is specified, intersection with rotated rectangle is computed.
	    intersectionWithLineFromCenterToPoint: function(p, angle) {

	        p = new Point(p);
	        var center = new Point(this.x + this.width / 2, this.y + this.height / 2);
	        var result;

	        if (angle) { p.rotate(center, angle); }

	        // (clockwise, starting from the top side)
	        var sides = [
	            this.topLine(),
	            this.rightLine(),
	            this.bottomLine(),
	            this.leftLine()
	        ];
	        var connector = new Line(center, p);

	        for (var i = sides.length - 1; i >= 0; --i) {
	            var intersection = sides[i].intersection(connector);
	            if (intersection !== null) {
	                result = intersection;
	                break;
	            }
	        }
	        if (result && angle) { result.rotate(center, -angle); }
	        return result;
	    },

	    leftLine: function() {

	        return new Line(this.topLeft(), this.bottomLeft());
	    },

	    leftMiddle: function() {

	        return new Point(this.x, this.y + this.height / 2);
	    },

	    maxRectScaleToFit: function(rect, origin) {

	        rect = new Rect(rect);
	        origin || (origin = rect.center());

	        var sx1, sx2, sx3, sx4, sy1, sy2, sy3, sy4;
	        var ox = origin.x;
	        var oy = origin.y;

	        // Here we find the maximal possible scale for all corner points (for x and y axis) of the rectangle,
	        // so when the scale is applied the point is still inside the rectangle.

	        sx1 = sx2 = sx3 = sx4 = sy1 = sy2 = sy3 = sy4 = Infinity;

	        // Top Left
	        var p1 = rect.topLeft();
	        if (p1.x < ox) {
	            sx1 = (this.x - ox) / (p1.x - ox);
	        }
	        if (p1.y < oy) {
	            sy1 = (this.y - oy) / (p1.y - oy);
	        }
	        // Bottom Right
	        var p2 = rect.bottomRight();
	        if (p2.x > ox) {
	            sx2 = (this.x + this.width - ox) / (p2.x - ox);
	        }
	        if (p2.y > oy) {
	            sy2 = (this.y + this.height - oy) / (p2.y - oy);
	        }
	        // Top Right
	        var p3 = rect.topRight();
	        if (p3.x > ox) {
	            sx3 = (this.x + this.width - ox) / (p3.x - ox);
	        }
	        if (p3.y < oy) {
	            sy3 = (this.y - oy) / (p3.y - oy);
	        }
	        // Bottom Left
	        var p4 = rect.bottomLeft();
	        if (p4.x < ox) {
	            sx4 = (this.x - ox) / (p4.x - ox);
	        }
	        if (p4.y > oy) {
	            sy4 = (this.y + this.height - oy) / (p4.y - oy);
	        }

	        return {
	            sx: min$5(sx1, sx2, sx3, sx4),
	            sy: min$5(sy1, sy2, sy3, sy4)
	        };
	    },

	    maxRectUniformScaleToFit: function(rect, origin) {

	        var scale = this.maxRectScaleToFit(rect, origin);
	        return min$5(scale.sx, scale.sy);
	    },

	    // Move and expand me.
	    // @param r {rectangle} representing deltas
	    moveAndExpand: function(r) {

	        this.x += r.x || 0;
	        this.y += r.y || 0;
	        this.width += r.width || 0;
	        this.height += r.height || 0;
	        return this;
	    },

	    // Normalize the rectangle; i.e., make it so that it has a non-negative width and height.
	    // If width < 0 the function swaps the left and right corners,
	    // and it swaps the top and bottom corners if height < 0
	    // like in http://qt-project.org/doc/qt-4.8/qrectf.html#normalized
	    normalize: function() {

	        var newx = this.x;
	        var newy = this.y;
	        var newwidth = this.width;
	        var newheight = this.height;
	        if (this.width < 0) {
	            newx = this.x + this.width;
	            newwidth = -this.width;
	        }
	        if (this.height < 0) {
	            newy = this.y + this.height;
	            newheight = -this.height;
	        }
	        this.x = newx;
	        this.y = newy;
	        this.width = newwidth;
	        this.height = newheight;
	        return this;
	    },

	    // Offset me by the specified amount.
	    offset: function(dx, dy) {

	        // pretend that this is a point and call offset()
	        // rewrites x and y according to dx and dy
	        return Point.prototype.offset.call(this, dx, dy);
	    },

	    origin: function() {

	        return new Point(this.x, this.y);
	    },

	    // @return {point} a point on my boundary nearest to the given point.
	    // @see Squeak Smalltalk, Rectangle>>pointNearestTo:
	    pointNearestToPoint: function(point) {

	        point = new Point(point);
	        if (this.containsPoint(point)) {
	            var side = this.sideNearestToPoint(point);
	            switch (side) {
	                case 'right':
	                    return new Point(this.x + this.width, point.y);
	                case 'left':
	                    return new Point(this.x, point.y);
	                case 'bottom':
	                    return new Point(point.x, this.y + this.height);
	                case 'top':
	                    return new Point(point.x, this.y);
	            }
	        }
	        return point.adhereToRect(this);
	    },

	    rightLine: function() {

	        return new Line(this.topRight(), this.bottomRight());
	    },

	    rightMiddle: function() {

	        return new Point(this.x + this.width, this.y + this.height / 2);
	    },

	    round: function(precision) {

	        var f = 1; // case 0
	        if (precision) {
	            switch (precision) {
	                case 1: f = 10; break;
	                case 2: f = 100; break;
	                case 3: f = 1000; break;
	                default: f = pow$2(10, precision); break;
	            }
	        }

	        this.x = round$3(this.x * f) / f;
	        this.y = round$3(this.y * f) / f;
	        this.width = round$3(this.width * f) / f;
	        this.height = round$3(this.height * f) / f;
	        return this;
	    },

	    // Scale rectangle with origin.
	    scale: function(sx, sy, origin) {

	        origin = this.origin().scale(sx, sy, origin);
	        this.x = origin.x;
	        this.y = origin.y;
	        this.width *= sx;
	        this.height *= sy;
	        return this;
	    },

	    // @return {string} (left|right|top|bottom) side which is nearest to point
	    // @see Squeak Smalltalk, Rectangle>>sideNearestTo:
	    sideNearestToPoint: function(point) {

	        point = new Point(point);
	        var distToLeft = point.x - this.x;
	        var distToRight = (this.x + this.width) - point.x;
	        var distToTop = point.y - this.y;
	        var distToBottom = (this.y + this.height) - point.y;
	        var closest = distToLeft;
	        var side = 'left';

	        if (distToRight < closest) {
	            closest = distToRight;
	            side = 'right';
	        }
	        if (distToTop < closest) {
	            closest = distToTop;
	            side = 'top';
	        }
	        if (distToBottom < closest) {
	            // closest = distToBottom;
	            side = 'bottom';
	        }
	        return side;
	    },

	    snapToGrid: function(gx, gy) {

	        var origin = this.origin().snapToGrid(gx, gy);
	        var corner = this.corner().snapToGrid(gx, gy);
	        this.x = origin.x;
	        this.y = origin.y;
	        this.width = corner.x - origin.x;
	        this.height = corner.y - origin.y;
	        return this;
	    },

	    toJSON: function() {

	        return { x: this.x, y: this.y, width: this.width, height: this.height };
	    },

	    topLine: function() {

	        return new Line(this.topLeft(), this.topRight());
	    },

	    topMiddle: function() {

	        return new Point(this.x + this.width / 2, this.y);
	    },

	    topRight: function() {

	        return new Point(this.x + this.width, this.y);
	    },

	    toString: function() {

	        return this.origin().toString() + ' ' + this.corner().toString();
	    },

	    // @return {rect} representing the union of both rectangles.
	    union: function(rect) {

	        return Rect.fromRectUnion(this, rect);
	    },

	    update: function(x, y, w, h) {

	        if ((Object(x) === x)) {
	            y = x.y;
	            w = x.width;
	            h = x.height;
	            x = x.x;
	        }

	        this.x = x || 0;
	        this.y = y || 0;
	        this.width = w || 0;
	        this.height = h || 0;
	        return this;
	    }
	};

	Rect.prototype.bottomRight = Rect.prototype.corner;

	Rect.prototype.topLeft = Rect.prototype.origin;

	Rect.prototype.translate = Rect.prototype.offset;

	// For backwards compatibility:
	var rect = Rect;

	function parsePoints(svgString) {

	    // Step 1: Discard surrounding spaces
	    var trimmedString = svgString.trim();
	    if (trimmedString === '') { return []; }

	    var points = [];

	    // Step 2: Split at commas (+ their surrounding spaces) or at multiple spaces
	    // ReDoS mitigation: Have an anchor at the beginning of each alternation
	    // Note: This doesn't simplify double (or more) commas - causes empty coords
	    // This regex is used by `split()`, so it doesn't need to use /g
	    var coords = trimmedString.split(/\b\s*,\s*|,\s*|\s+/);

	    var numCoords = coords.length;
	    for (var i = 0; i < numCoords; i += 2) {
	        // Step 3: Convert each coord to number
	        // Note: If the coord cannot be converted to a number, it will be `NaN`
	        // Note: If the coord is empty ("", e.g. from ",," input), it will be `0`
	        // Note: If we end up with an odd number of coords, the last point's second coord will be `NaN`
	        points.push({ x: +coords[i], y: +coords[i + 1] });
	    }
	    return points;
	}

	function clonePoints(points) {
	    var numPoints = points.length;
	    if (numPoints === 0) { return []; }
	    var newPoints = [];
	    for (var i = 0; i < numPoints; i++) {
	        var point = points[i].clone();
	        newPoints.push(point);
	    }
	    return newPoints;
	}

	// Returns a convex-hull polyline from this polyline.
	// Implements the Graham scan (https://en.wikipedia.org/wiki/Graham_scan).
	// Output polyline starts at the first element of the original polyline that is on the hull, then continues clockwise.
	// Minimal polyline is found (only vertices of the hull are reported, no collinear points).
	function convexHull(points) {

	    var abs = Math.abs;

	    var i;
	    var n;

	    var numPoints = points.length;
	    if (numPoints === 0) { return []; } // if points array is empty

	    // step 1: find the starting point - point with the lowest y (if equality, highest x)
	    var startPoint;
	    for (i = 0; i < numPoints; i++) {
	        if (startPoint === undefined) {
	            // if this is the first point we see, set it as start point
	            startPoint = points[i];

	        } else if (points[i].y < startPoint.y) {
	            // start point should have lowest y from all points
	            startPoint = points[i];

	        } else if ((points[i].y === startPoint.y) && (points[i].x > startPoint.x)) {
	            // if two points have the lowest y, choose the one that has highest x
	            // there are no points to the right of startPoint - no ambiguity about theta 0
	            // if there are several coincident start point candidates, first one is reported
	            startPoint = points[i];
	        }
	    }

	    // step 2: sort the list of points
	    // sorting by angle between line from startPoint to point and the x-axis (theta)

	    // step 2a: create the point records = [point, originalIndex, angle]
	    var sortedPointRecords = [];
	    for (i = 0; i < numPoints; i++) {

	        var angle = startPoint.theta(points[i]);
	        if (angle === 0) {
	            angle = 360; // give highest angle to start point
	            // the start point will end up at end of sorted list
	            // the start point will end up at beginning of hull points list
	        }

	        var entry = [points[i], i, angle];
	        sortedPointRecords.push(entry);
	    }

	    // step 2b: sort the list in place
	    sortedPointRecords.sort(function(record1, record2) {
	        // returning a negative number here sorts record1 before record2
	        // if first angle is smaller than second, first angle should come before second

	        var sortOutput = record1[2] - record2[2];  // negative if first angle smaller
	        if (sortOutput === 0) {
	            // if the two angles are equal, sort by originalIndex
	            sortOutput = record2[1] - record1[1]; // negative if first index larger
	            // coincident points will be sorted in reverse-numerical order
	            // so the coincident points with lower original index will be considered first
	        }

	        return sortOutput;
	    });

	    // step 2c: duplicate start record from the top of the stack to the bottom of the stack
	    if (sortedPointRecords.length > 2) {
	        var startPointRecord = sortedPointRecords[sortedPointRecords.length - 1];
	        sortedPointRecords.unshift(startPointRecord);
	    }

	    // step 3a: go through sorted points in order and find those with right turns
	    // we want to get our results in clockwise order
	    var insidePoints = {}; // dictionary of points with left turns - cannot be on the hull
	    var hullPointRecords = []; // stack of records with right turns - hull point candidates

	    var currentPointRecord;
	    var currentPoint;
	    var lastHullPointRecord;
	    var lastHullPoint;
	    var secondLastHullPointRecord;
	    var secondLastHullPoint;
	    while (sortedPointRecords.length !== 0) {

	        currentPointRecord = sortedPointRecords.pop();
	        currentPoint = currentPointRecord[0];

	        // check if point has already been discarded
	        // keys for insidePoints are stored in the form 'point.x@point.y@@originalIndex'
	        if (insidePoints.hasOwnProperty(currentPointRecord[0] + '@@' + currentPointRecord[1])) {
	            // this point had an incorrect turn at some previous iteration of this loop
	            // this disqualifies it from possibly being on the hull
	            continue;
	        }

	        var correctTurnFound = false;
	        while (!correctTurnFound) {

	            if (hullPointRecords.length < 2) {
	                // not enough points for comparison, just add current point
	                hullPointRecords.push(currentPointRecord);
	                correctTurnFound = true;

	            } else {
	                lastHullPointRecord = hullPointRecords.pop();
	                lastHullPoint = lastHullPointRecord[0];
	                secondLastHullPointRecord = hullPointRecords.pop();
	                secondLastHullPoint = secondLastHullPointRecord[0];

	                var crossProduct = secondLastHullPoint.cross(lastHullPoint, currentPoint);

	                if (crossProduct < 0) {
	                    // found a right turn
	                    hullPointRecords.push(secondLastHullPointRecord);
	                    hullPointRecords.push(lastHullPointRecord);
	                    hullPointRecords.push(currentPointRecord);
	                    correctTurnFound = true;

	                } else if (crossProduct === 0) {
	                    // the three points are collinear
	                    // three options:
	                    // there may be a 180 or 0 degree angle at lastHullPoint
	                    // or two of the three points are coincident
	                    var THRESHOLD = 1e-10; // we have to take rounding errors into account
	                    var angleBetween = lastHullPoint.angleBetween(secondLastHullPoint, currentPoint);
	                    if (abs(angleBetween - 180) < THRESHOLD) { // rounding around 180 to 180
	                        // if the cross product is 0 because the angle is 180 degrees
	                        // discard last hull point (add to insidePoints)
	                        //insidePoints.unshift(lastHullPoint);
	                        insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint;
	                        // reenter second-to-last hull point (will be last at next iter)
	                        hullPointRecords.push(secondLastHullPointRecord);
	                        // do not do anything with current point
	                        // correct turn not found

	                    } else if (lastHullPoint.equals(currentPoint) || secondLastHullPoint.equals(lastHullPoint)) {
	                        // if the cross product is 0 because two points are the same
	                        // discard last hull point (add to insidePoints)
	                        //insidePoints.unshift(lastHullPoint);
	                        insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint;
	                        // reenter second-to-last hull point (will be last at next iter)
	                        hullPointRecords.push(secondLastHullPointRecord);
	                        // do not do anything with current point
	                        // correct turn not found

	                    } else if (abs(((angleBetween + 1) % 360) - 1) < THRESHOLD) { // rounding around 0 and 360 to 0
	                        // if the cross product is 0 because the angle is 0 degrees
	                        // remove last hull point from hull BUT do not discard it
	                        // reenter second-to-last hull point (will be last at next iter)
	                        hullPointRecords.push(secondLastHullPointRecord);
	                        // put last hull point back into the sorted point records list
	                        sortedPointRecords.push(lastHullPointRecord);
	                        // we are switching the order of the 0deg and 180deg points
	                        // correct turn not found
	                    }

	                } else {
	                    // found a left turn
	                    // discard last hull point (add to insidePoints)
	                    //insidePoints.unshift(lastHullPoint);
	                    insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint;
	                    // reenter second-to-last hull point (will be last at next iter of loop)
	                    hullPointRecords.push(secondLastHullPointRecord);
	                    // do not do anything with current point
	                    // correct turn not found
	                }
	            }
	        }
	    }
	    // at this point, hullPointRecords contains the output points in clockwise order
	    // the points start with lowest-y,highest-x startPoint, and end at the same point

	    // step 3b: remove duplicated startPointRecord from the end of the array
	    if (hullPointRecords.length > 2) {
	        hullPointRecords.pop();
	    }

	    // step 4: find the lowest originalIndex record and put it at the beginning of hull
	    var lowestHullIndex; // the lowest originalIndex on the hull
	    var indexOfLowestHullIndexRecord = -1; // the index of the record with lowestHullIndex
	    n = hullPointRecords.length;
	    for (i = 0; i < n; i++) {

	        var currentHullIndex = hullPointRecords[i][1];

	        if (lowestHullIndex === undefined || currentHullIndex < lowestHullIndex) {
	            lowestHullIndex = currentHullIndex;
	            indexOfLowestHullIndexRecord = i;
	        }
	    }

	    var hullPointRecordsReordered = [];
	    if (indexOfLowestHullIndexRecord > 0) {
	        var newFirstChunk = hullPointRecords.slice(indexOfLowestHullIndexRecord);
	        var newSecondChunk = hullPointRecords.slice(0, indexOfLowestHullIndexRecord);
	        hullPointRecordsReordered = newFirstChunk.concat(newSecondChunk);

	    } else {
	        hullPointRecordsReordered = hullPointRecords;
	    }

	    var hullPoints = [];
	    n = hullPointRecordsReordered.length;
	    for (i = 0; i < n; i++) {
	        hullPoints.push(hullPointRecordsReordered[i][0]);
	    }

	    return hullPoints;
	}

	var Polyline = function(points) {

	    if (!(this instanceof Polyline)) {
	        return new Polyline(points);
	    }

	    if (typeof points === 'string') {
	        return new Polyline.parse(points);
	    }

	    this.points = (Array.isArray(points) ? points.map(Point) : []);
	};

	Polyline.parse = function(svgString) {
	    return new Polyline(parsePoints(svgString));
	};

	Polyline.fromRect = function(rect) {
	    return new Polyline([
	        rect.topLeft(),
	        rect.topRight(),
	        rect.bottomRight(),
	        rect.bottomLeft(),
	        rect.topLeft() ]);
	};

	Polyline.prototype = {

	    type: types.Polyline,

	    bbox: function() {

	        var x1 = Infinity;
	        var x2 = -Infinity;
	        var y1 = Infinity;
	        var y2 = -Infinity;

	        var points = this.points;
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty

	        for (var i = 0; i < numPoints; i++) {

	            var point = points[i];
	            var x = point.x;
	            var y = point.y;

	            if (x < x1) { x1 = x; }
	            if (x > x2) { x2 = x; }
	            if (y < y1) { y1 = y; }
	            if (y > y2) { y2 = y; }
	        }

	        return new Rect(x1, y1, x2 - x1, y2 - y1);
	    },

	    clone: function() {
	        return new Polyline(clonePoints(this.points));
	    },

	    closestPoint: function(p) {

	        var cpLength = this.closestPointLength(p);

	        return this.pointAtLength(cpLength);
	    },

	    closestPointLength: function(p) {

	        var points = this.lengthPoints();
	        var numPoints = points.length;
	        if (numPoints === 0) { return 0; } // if points array is empty
	        if (numPoints === 1) { return 0; } // if there is only one point

	        var cpLength;
	        var minSqrDistance = Infinity;
	        var length = 0;
	        var n = numPoints - 1;
	        for (var i = 0; i < n; i++) {

	            var line = new Line(points[i], points[i + 1]);
	            var lineLength = line.length();

	            var cpNormalizedLength = line.closestPointNormalizedLength(p);
	            var cp = line.pointAt(cpNormalizedLength);

	            var sqrDistance = cp.squaredDistance(p);
	            if (sqrDistance < minSqrDistance) {
	                minSqrDistance = sqrDistance;
	                cpLength = length + (cpNormalizedLength * lineLength);
	            }

	            length += lineLength;
	        }

	        return cpLength;
	    },

	    closestPointNormalizedLength: function(p) {

	        var cpLength = this.closestPointLength(p);
	        if (cpLength === 0) { return 0; } // shortcut

	        var length = this.length();
	        if (length === 0) { return 0; } // prevents division by zero

	        return cpLength / length;
	    },

	    closestPointTangent: function(p) {

	        var cpLength = this.closestPointLength(p);

	        return this.tangentAtLength(cpLength);
	    },

	    // Returns `true` if the area surrounded by the polyline contains the point `p`.
	    // Implements the even-odd SVG algorithm (self-intersections are "outside").
	    // (Uses horizontal rays to the right of `p` to look for intersections.)
	    // Closes open polylines (always imagines a final closing segment).
	    containsPoint: function(p) {

	        var points = this.points;
	        var numPoints = points.length;
	        if (numPoints === 0) { return false; } // shortcut (this polyline has no points)

	        var x = p.x;
	        var y = p.y;

	        // initialize a final closing segment by creating one from last-first points on polyline
	        var startIndex = numPoints - 1; // start of current polyline segment
	        var endIndex = 0; // end of current polyline segment
	        var numIntersections = 0;
	        var segment = new Line();
	        var ray = new Line();
	        var rayEnd = new Point();
	        for (; endIndex < numPoints; endIndex++) {
	            var start = points[startIndex];
	            var end = points[endIndex];
	            if (p.equals(start)) { return true; } // shortcut (`p` is a point on polyline)
	            // current polyline segment
	            segment.start = start;
	            segment.end = end;
	            if (segment.containsPoint(p)) { return true; } // shortcut (`p` lies on a polyline segment)

	            // do we have an intersection?
	            if (((y <= start.y) && (y > end.y)) || ((y > start.y) && (y <= end.y))) {
	                // this conditional branch IS NOT entered when `segment` is collinear/coincident with `ray`
	                // (when `y === start.y === end.y`)
	                // this conditional branch IS entered when `segment` touches `ray` at only one point
	                // (e.g. when `y === start.y !== end.y`)
	                // since this branch is entered again for the following segment, the two touches cancel out

	                var xDifference = (((start.x - x) > (end.x - x)) ? (start.x - x) : (end.x - x));
	                if (xDifference >= 0) {
	                    // segment lies at least partially to the right of `p`
	                    rayEnd.x = x + xDifference;
	                    rayEnd.y = y; // right
	                    ray.start = p;
	                    ray.end = rayEnd;
	                    if (segment.intersect(ray)) {
	                        // an intersection was detected to the right of `p`
	                        numIntersections++;
	                    }
	                } // else: `segment` lies completely to the left of `p` (i.e. no intersection to the right)
	            }

	            // move to check the next polyline segment
	            startIndex = endIndex;
	        }

	        // returns `true` for odd numbers of intersections (even-odd algorithm)
	        return ((numIntersections % 2) === 1);
	    },

	    close: function() {
	        var ref = this;
	        var start = ref.start;
	        var end = ref.end;
	        var points = ref.points;
	        if (start && end && !start.equals(end)) {
	            points.push(start.clone());
	        }
	        return this;
	    },

	    lengthPoints: function() {
	        return this.points;
	    },

	    convexHull: function() {
	        return new Polyline(convexHull(this.points));
	    },

	    // Checks whether two polylines are exactly the same.
	    // If `p` is undefined or null, returns false.
	    equals: function(p) {

	        if (!p) { return false; }

	        var points = this.points;
	        var otherPoints = p.points;

	        var numPoints = points.length;
	        if (otherPoints.length !== numPoints) { return false; } // if the two polylines have different number of points, they cannot be equal

	        for (var i = 0; i < numPoints; i++) {

	            var point = points[i];
	            var otherPoint = p.points[i];

	            // as soon as an inequality is found in points, return false
	            if (!point.equals(otherPoint)) { return false; }
	        }

	        // if no inequality found in points, return true
	        return true;
	    },

	    intersectionWithLine: function(l) {
	        var line = new Line(l);
	        var intersections = [];
	        var points = this.lengthPoints();
	        var l2 = new Line();
	        for (var i = 0, n = points.length - 1; i < n; i++) {
	            l2.start = points[i];
	            l2.end = points[i + 1];
	            var int = line.intersectionWithLine(l2);
	            if (int) { intersections.push(int[0]); }
	        }
	        return (intersections.length > 0) ? intersections : null;
	    },

	    isDifferentiable: function() {

	        var points = this.points;
	        var numPoints = points.length;
	        if (numPoints === 0) { return false; }

	        var line = new Line();
	        var n = numPoints - 1;
	        for (var i = 0; i < n; i++) {
	            line.start = points[i];
	            line.end = points[i + 1];
	            // as soon as a differentiable line is found between two points, return true
	            if (line.isDifferentiable()) { return true; }
	        }

	        // if no differentiable line is found between pairs of points, return false
	        return false;
	    },

	    length: function() {

	        var points = this.lengthPoints();
	        var numPoints = points.length;
	        if (numPoints === 0) { return 0; } // if points array is empty

	        var length = 0;
	        var n = numPoints - 1;
	        for (var i = 0; i < n; i++) {
	            length += points[i].distance(points[i + 1]);
	        }

	        return length;
	    },

	    pointAt: function(ratio) {

	        var points = this.lengthPoints();
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty
	        if (numPoints === 1) { return points[0].clone(); } // if there is only one point

	        if (ratio <= 0) { return points[0].clone(); }
	        if (ratio >= 1) { return points[numPoints - 1].clone(); }

	        var polylineLength = this.length();
	        var length = polylineLength * ratio;

	        return this.pointAtLength(length);
	    },

	    pointAtLength: function(length) {

	        var points = this.lengthPoints();
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty
	        if (numPoints === 1) { return points[0].clone(); } // if there is only one point

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        var l = 0;
	        var n = numPoints - 1;
	        for (var i = 0; i < n; i++) {
	            var index = (fromStart ? i : (n - 1 - i));

	            var a = points[index];
	            var b = points[index + 1];
	            var line = new Line(a, b);
	            var d = a.distance(b);

	            if (length <= (l + d)) {
	                return line.pointAtLength((fromStart ? 1 : -1) * (length - l));
	            }

	            l += d;
	        }

	        // if length requested is higher than the length of the polyline, return last endpoint
	        var lastPoint = (fromStart ? points[numPoints - 1] : points[0]);
	        return lastPoint.clone();
	    },

	    round: function(precision) {

	        var points = this.points;
	        var numPoints = points.length;

	        for (var i = 0; i < numPoints; i++) {
	            points[i].round(precision);
	        }

	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        var points = this.points;
	        var numPoints = points.length;

	        for (var i = 0; i < numPoints; i++) {
	            points[i].scale(sx, sy, origin);
	        }

	        return this;
	    },

	    simplify: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        var points = this.points;
	        if (points.length < 3) { return this; } // we need at least 3 points

	        // TODO: we may also accept startIndex and endIndex to specify where to start and end simplification
	        var threshold = opt.threshold || 0; // = max distance of middle point from chord to be simplified

	        // start at the beginning of the polyline and go forward
	        var currentIndex = 0;
	        // we need at least one intermediate point (3 points) in every iteration
	        // as soon as that stops being true, we know we reached the end of the polyline
	        while (points[currentIndex + 2]) {
	            var firstIndex = currentIndex;
	            var middleIndex = (currentIndex + 1);
	            var lastIndex = (currentIndex + 2);

	            var firstPoint = points[firstIndex];
	            var middlePoint = points[middleIndex];
	            var lastPoint = points[lastIndex];

	            var chord = new Line(firstPoint, lastPoint); // = connection between first and last point
	            var closestPoint = chord.closestPoint(middlePoint); // = closest point on chord from middle point
	            var closestPointDistance = closestPoint.distance(middlePoint);
	            if (closestPointDistance <= threshold) {
	                // middle point is close enough to the chord = simplify
	                // 1) remove middle point:
	                points.splice(middleIndex, 1);
	                // 2) in next iteration, investigate the newly-created triplet of points
	                //    - do not change `currentIndex`
	                //    = (first point stays, point after removed point becomes middle point)
	            } else {
	                // middle point is far from the chord
	                // 1) preserve middle point
	                // 2) in next iteration, move `currentIndex` by one step:
	                currentIndex += 1;
	                //    = (point after first point becomes first point)
	            }
	        }

	        // `points` array was modified in-place
	        return this;
	    },

	    tangentAt: function(ratio) {

	        var points = this.lengthPoints();
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty
	        if (numPoints === 1) { return null; } // if there is only one point

	        if (ratio < 0) { ratio = 0; }
	        if (ratio > 1) { ratio = 1; }

	        var polylineLength = this.length();
	        var length = polylineLength * ratio;

	        return this.tangentAtLength(length);
	    },

	    tangentAtLength: function(length) {

	        var points = this.lengthPoints();
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty
	        if (numPoints === 1) { return null; } // if there is only one point

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        var lastValidLine; // differentiable (with a tangent)
	        var l = 0; // length so far
	        var n = numPoints - 1;
	        for (var i = 0; i < n; i++) {
	            var index = (fromStart ? i : (n - 1 - i));

	            var a = points[index];
	            var b = points[index + 1];
	            var line = new Line(a, b);
	            var d = a.distance(b);

	            if (line.isDifferentiable()) { // has a tangent line (line length is not 0)
	                if (length <= (l + d)) {
	                    return line.tangentAtLength((fromStart ? 1 : -1) * (length - l));
	                }

	                lastValidLine = line;
	            }

	            l += d;
	        }

	        // if length requested is higher than the length of the polyline, return last valid endpoint
	        if (lastValidLine) {
	            var ratio = (fromStart ? 1 : 0);
	            return lastValidLine.tangentAt(ratio);
	        }

	        // if no valid line, return null
	        return null;
	    },

	    toString: function() {

	        return this.points + '';
	    },

	    translate: function(tx, ty) {

	        var points = this.points;
	        var numPoints = points.length;

	        for (var i = 0; i < numPoints; i++) {
	            points[i].translate(tx, ty);
	        }

	        return this;
	    },

	    // Return svgString that can be used to recreate this line.
	    serialize: function() {

	        var points = this.points;
	        var numPoints = points.length;
	        if (numPoints === 0) { return ''; } // if points array is empty

	        var output = '';
	        for (var i = 0; i < numPoints; i++) {

	            var point = points[i];
	            output += point.x + ',' + point.y + ' ';
	        }

	        return output.trim();
	    }
	};

	Object.defineProperty(Polyline.prototype, 'start', {
	    // Getter for the first point of the polyline.

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        var points = this.points;
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty

	        return this.points[0];
	    },
	});

	Object.defineProperty(Polyline.prototype, 'end', {
	    // Getter for the last point of the polyline.

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        var points = this.points;
	        var numPoints = points.length;
	        if (numPoints === 0) { return null; } // if points array is empty

	        return this.points[numPoints - 1];
	    },
	});

	var abs$2 = Math.abs;
	var sqrt$2 = Math.sqrt;
	var min$6 = Math.min;
	var max$4 = Math.max;
	var pow$3 = Math.pow;

	var Curve = function(p1, p2, p3, p4) {

	    if (!(this instanceof Curve)) {
	        return new Curve(p1, p2, p3, p4);
	    }

	    if (p1 instanceof Curve) {
	        return new Curve(p1.start, p1.controlPoint1, p1.controlPoint2, p1.end);
	    }

	    this.start = new Point(p1);
	    this.controlPoint1 = new Point(p2);
	    this.controlPoint2 = new Point(p3);
	    this.end = new Point(p4);
	};

	// Curve passing through points.
	// Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx).
	// @param {array} points Array of points through which the smooth line will go.
	// @return {array} curves.
	Curve.throughPoints = (function() {

	    // Get open-ended Bezier Spline Control Points.
	    // @param knots Input Knot Bezier spline points (At least two points!).
	    // @param firstControlPoints Output First Control points. Array of knots.length - 1 length.
	    // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length.
	    function getCurveControlPoints(knots) {

	        var firstControlPoints = [];
	        var secondControlPoints = [];
	        var n = knots.length - 1;
	        var i;

	        // Special case: Bezier curve should be a straight line.
	        if (n == 1) {
	            // 3P1 = 2P0 + P3
	            firstControlPoints[0] = new Point(
	                (2 * knots[0].x + knots[1].x) / 3,
	                (2 * knots[0].y + knots[1].y) / 3
	            );

	            // P2 = 2P1 – P0
	            secondControlPoints[0] = new Point(
	                2 * firstControlPoints[0].x - knots[0].x,
	                2 * firstControlPoints[0].y - knots[0].y
	            );

	            return [firstControlPoints, secondControlPoints];
	        }

	        // Calculate first Bezier control points.
	        // Right hand side vector.
	        var rhs = [];

	        // Set right hand side X values.
	        for (i = 1; i < n - 1; i++) {
	            rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
	        }

	        rhs[0] = knots[0].x + 2 * knots[1].x;
	        rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;

	        // Get first control points X-values.
	        var x = getFirstControlPoints(rhs);

	        // Set right hand side Y values.
	        for (i = 1; i < n - 1; ++i) {
	            rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
	        }

	        rhs[0] = knots[0].y + 2 * knots[1].y;
	        rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;

	        // Get first control points Y-values.
	        var y = getFirstControlPoints(rhs);

	        // Fill output arrays.
	        for (i = 0; i < n; i++) {
	            // First control point.
	            firstControlPoints.push(new Point(x[i], y[i]));

	            // Second control point.
	            if (i < n - 1) {
	                secondControlPoints.push(new Point(
	                    2 * knots [i + 1].x - x[i + 1],
	                    2 * knots[i + 1].y - y[i + 1]
	                ));

	            } else {
	                secondControlPoints.push(new Point(
	                    (knots[n].x + x[n - 1]) / 2,
	                    (knots[n].y + y[n - 1]) / 2
	                ));
	            }
	        }

	        return [firstControlPoints, secondControlPoints];
	    }

	    // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
	    // @param rhs Right hand side vector.
	    // @return Solution vector.
	    function getFirstControlPoints(rhs) {

	        var n = rhs.length;
	        // `x` is a solution vector.
	        var x = [];
	        var tmp = [];
	        var b = 2.0;

	        x[0] = rhs[0] / b;

	        // Decomposition and forward substitution.
	        for (var i = 1; i < n; i++) {
	            tmp[i] = 1 / b;
	            b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
	            x[i] = (rhs[i] - x[i - 1]) / b;
	        }

	        for (i = 1; i < n; i++) {
	            // Backsubstitution.
	            x[n - i - 1] -= tmp[n - i] * x[n - i];
	        }

	        return x;
	    }

	    return function(points) {

	        if (!points || (Array.isArray(points) && points.length < 2)) {
	            throw new Error('At least 2 points are required');
	        }

	        var controlPoints = getCurveControlPoints(points);

	        var curves = [];
	        var n = controlPoints[0].length;
	        for (var i = 0; i < n; i++) {

	            var controlPoint1 = new Point(controlPoints[0][i].x, controlPoints[0][i].y);
	            var controlPoint2 = new Point(controlPoints[1][i].x, controlPoints[1][i].y);

	            curves.push(new Curve(points[i], controlPoint1, controlPoint2, points[i + 1]));
	        }

	        return curves;
	    };
	})();

	Curve.prototype = {

	    type: types.Curve,

	    // Returns a bbox that tightly envelops the curve.
	    bbox: function() {

	        var start = this.start;
	        var controlPoint1 = this.controlPoint1;
	        var controlPoint2 = this.controlPoint2;
	        var end = this.end;

	        var x0 = start.x;
	        var y0 = start.y;
	        var x1 = controlPoint1.x;
	        var y1 = controlPoint1.y;
	        var x2 = controlPoint2.x;
	        var y2 = controlPoint2.y;
	        var x3 = end.x;
	        var y3 = end.y;

	        var points = new Array(); // local extremes
	        var tvalues = new Array(); // t values of local extremes
	        var bounds = [new Array(), new Array()];

	        var a, b, c, t;
	        var t1, t2;
	        var b2ac, sqrtb2ac;

	        for (var i = 0; i < 2; ++i) {

	            if (i === 0) {
	                b = 6 * x0 - 12 * x1 + 6 * x2;
	                a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
	                c = 3 * x1 - 3 * x0;

	            } else {
	                b = 6 * y0 - 12 * y1 + 6 * y2;
	                a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
	                c = 3 * y1 - 3 * y0;
	            }

	            if (abs$2(a) < 1e-12) { // Numerical robustness
	                if (abs$2(b) < 1e-12) { // Numerical robustness
	                    continue;
	                }

	                t = -c / b;
	                if ((0 < t) && (t < 1)) { tvalues.push(t); }

	                continue;
	            }

	            b2ac = b * b - 4 * c * a;
	            sqrtb2ac = sqrt$2(b2ac);

	            if (b2ac < 0) { continue; }

	            t1 = (-b + sqrtb2ac) / (2 * a);
	            if ((0 < t1) && (t1 < 1)) { tvalues.push(t1); }

	            t2 = (-b - sqrtb2ac) / (2 * a);
	            if ((0 < t2) && (t2 < 1)) { tvalues.push(t2); }
	        }

	        var j = tvalues.length;
	        var jlen = j;
	        var mt;
	        var x, y;

	        while (j--) {
	            t = tvalues[j];
	            mt = 1 - t;

	            x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
	            bounds[0][j] = x;

	            y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
	            bounds[1][j] = y;

	            points[j] = { X: x, Y: y };
	        }

	        tvalues[jlen] = 0;
	        tvalues[jlen + 1] = 1;

	        points[jlen] = { X: x0, Y: y0 };
	        points[jlen + 1] = { X: x3, Y: y3 };

	        bounds[0][jlen] = x0;
	        bounds[1][jlen] = y0;

	        bounds[0][jlen + 1] = x3;
	        bounds[1][jlen + 1] = y3;

	        tvalues.length = jlen + 2;
	        bounds[0].length = jlen + 2;
	        bounds[1].length = jlen + 2;
	        points.length = jlen + 2;

	        var left = min$6.apply(null, bounds[0]);
	        var top = min$6.apply(null, bounds[1]);
	        var right = max$4.apply(null, bounds[0]);
	        var bottom = max$4.apply(null, bounds[1]);

	        return new Rect(left, top, (right - left), (bottom - top));
	    },

	    clone: function() {

	        return new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
	    },

	    // Returns the point on the curve closest to point `p`
	    closestPoint: function(p, opt) {

	        return this.pointAtT(this.closestPointT(p, opt));
	    },

	    closestPointLength: function(p, opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        var localOpt = { precision: precision, subdivisions: subdivisions };

	        return this.lengthAtT(this.closestPointT(p, localOpt), localOpt);
	    },

	    closestPointNormalizedLength: function(p, opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        var localOpt = { precision: precision, subdivisions: subdivisions };

	        var cpLength = this.closestPointLength(p, localOpt);
	        if (!cpLength) { return 0; }

	        var length = this.length(localOpt);
	        if (length === 0) { return 0; }

	        return cpLength / length;
	    },

	    // Returns `t` of the point on the curve closest to point `p`
	    closestPointT: function(p, opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        // does not use localOpt

	        // identify the subdivision that contains the point:
	        var investigatedSubdivision;
	        var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced
	        var investigatedSubdivisionEndT;
	        var distFromStart; // distance of point from start of baseline
	        var distFromEnd; // distance of point from end of baseline
	        var chordLength; // distance between start and end of the subdivision
	        var minSumDist; // lowest observed sum of the two distances
	        var n = subdivisions.length;
	        var subdivisionSize = (n ? (1 / n) : 0);
	        for (var i = 0; i < n; i++) {

	            var currentSubdivision = subdivisions[i];

	            var startDist = currentSubdivision.start.distance(p);
	            var endDist = currentSubdivision.end.distance(p);
	            var sumDist = startDist + endDist;

	            // check that the point is closest to current subdivision and not any other
	            if (!minSumDist || (sumDist < minSumDist)) {
	                investigatedSubdivision = currentSubdivision;

	                investigatedSubdivisionStartT = i * subdivisionSize;
	                investigatedSubdivisionEndT = (i + 1) * subdivisionSize;

	                distFromStart = startDist;
	                distFromEnd = endDist;

	                chordLength = currentSubdivision.start.distance(currentSubdivision.end);

	                minSumDist = sumDist;
	            }
	        }

	        var precisionRatio = pow$3(10, -precision);

	        // recursively divide investigated subdivision:
	        // until distance between baselinePoint and closest path endpoint is within 10^(-precision)
	        // then return the closest endpoint of that final subdivision
	        while (true) {

	            // check if we have reached at least one required observed precision
	            // - calculated as: the difference in distances from point to start and end divided by the distance
	            // - note that this function is not monotonic = it doesn't converge stably but has "teeth"
	            // - the function decreases while one of the endpoints is fixed but "jumps" whenever we switch
	            // - this criterion works well for points lying far away from the curve
	            var startPrecisionRatio = (distFromStart ? (abs$2(distFromStart - distFromEnd) / distFromStart) : 0);
	            var endPrecisionRatio = (distFromEnd ? (abs$2(distFromStart - distFromEnd) / distFromEnd) : 0);
	            var hasRequiredPrecision = ((startPrecisionRatio < precisionRatio) || (endPrecisionRatio < precisionRatio));

	            // check if we have reached at least one required minimal distance
	            // - calculated as: the subdivision chord length multiplied by precisionRatio
	            // - calculation is relative so it will work for arbitrarily large/small curves and their subdivisions
	            // - this is a backup criterion that works well for points lying "almost at" the curve
	            var hasMinimalStartDistance = (distFromStart ? (distFromStart < (chordLength * precisionRatio)) : true);
	            var hasMinimalEndDistance = (distFromEnd ? (distFromEnd < (chordLength * precisionRatio)) : true);
	            var hasMinimalDistance = (hasMinimalStartDistance || hasMinimalEndDistance);

	            // do we stop now?
	            if (hasRequiredPrecision || hasMinimalDistance) {
	                return ((distFromStart <= distFromEnd) ? investigatedSubdivisionStartT : investigatedSubdivisionEndT);
	            }

	            // otherwise, set up for next iteration
	            var divided = investigatedSubdivision.divide(0.5);
	            subdivisionSize /= 2;

	            var startDist1 = divided[0].start.distance(p);
	            var endDist1 = divided[0].end.distance(p);
	            var sumDist1 = startDist1 + endDist1;

	            var startDist2 = divided[1].start.distance(p);
	            var endDist2 = divided[1].end.distance(p);
	            var sumDist2 = startDist2 + endDist2;

	            if (sumDist1 <= sumDist2) {
	                investigatedSubdivision = divided[0];

	                investigatedSubdivisionEndT -= subdivisionSize; // subdivisionSize was already halved

	                distFromStart = startDist1;
	                distFromEnd = endDist1;

	            } else {
	                investigatedSubdivision = divided[1];

	                investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved

	                distFromStart = startDist2;
	                distFromEnd = endDist2;
	            }
	        }
	    },

	    closestPointTangent: function(p, opt) {

	        return this.tangentAtT(this.closestPointT(p, opt));
	    },

	    // Returns `true` if the area surrounded by the curve contains the point `p`.
	    // Implements the even-odd algorithm (self-intersections are "outside").
	    // Closes open curves (always imagines a closing segment).
	    // Precision may be adjusted by passing an `opt` object.
	    containsPoint: function(p, opt) {

	        var polyline = this.toPolyline(opt);
	        return polyline.containsPoint(p);
	    },

	    // Divides the curve into two at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
	    // For a function that uses `t`, use Curve.divideAtT().
	    divideAt: function(ratio, opt) {

	        if (ratio <= 0) { return this.divideAtT(0); }
	        if (ratio >= 1) { return this.divideAtT(1); }

	        var t = this.tAt(ratio, opt);

	        return this.divideAtT(t);
	    },

	    // Divides the curve into two at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
	    divideAtLength: function(length, opt) {

	        var t = this.tAtLength(length, opt);

	        return this.divideAtT(t);
	    },

	    // Divides the curve into two at point defined by `t` between 0 and 1.
	    // Using de Casteljau's algorithm (http://math.stackexchange.com/a/317867).
	    // Additional resource: https://pomax.github.io/bezierinfo/#decasteljau
	    divideAtT: function(t) {

	        var start = this.start;
	        var controlPoint1 = this.controlPoint1;
	        var controlPoint2 = this.controlPoint2;
	        var end = this.end;

	        // shortcuts for `t` values that are out of range
	        if (t <= 0) {
	            return [
	                new Curve(start, start, start, start),
	                new Curve(start, controlPoint1, controlPoint2, end)
	            ];
	        }

	        if (t >= 1) {
	            return [
	                new Curve(start, controlPoint1, controlPoint2, end),
	                new Curve(end, end, end, end)
	            ];
	        }

	        var dividerPoints = this.getSkeletonPoints(t);

	        var startControl1 = dividerPoints.startControlPoint1;
	        var startControl2 = dividerPoints.startControlPoint2;
	        var divider = dividerPoints.divider;
	        var dividerControl1 = dividerPoints.dividerControlPoint1;
	        var dividerControl2 = dividerPoints.dividerControlPoint2;

	        // return array with two new curves
	        return [
	            new Curve(start, startControl1, startControl2, divider),
	            new Curve(divider, dividerControl1, dividerControl2, end)
	        ];
	    },

	    // Returns the distance between the curve's start and end points.
	    endpointDistance: function() {

	        return this.start.distance(this.end);
	    },

	    // Checks whether two curves are exactly the same.
	    equals: function(c) {

	        return !!c &&
	            this.start.x === c.start.x &&
	            this.start.y === c.start.y &&
	            this.controlPoint1.x === c.controlPoint1.x &&
	            this.controlPoint1.y === c.controlPoint1.y &&
	            this.controlPoint2.x === c.controlPoint2.x &&
	            this.controlPoint2.y === c.controlPoint2.y &&
	            this.end.x === c.end.x &&
	            this.end.y === c.end.y;
	    },

	    // Returns five helper points necessary for curve division.
	    getSkeletonPoints: function(t) {

	        var start = this.start;
	        var control1 = this.controlPoint1;
	        var control2 = this.controlPoint2;
	        var end = this.end;

	        // shortcuts for `t` values that are out of range
	        if (t <= 0) {
	            return {
	                startControlPoint1: start.clone(),
	                startControlPoint2: start.clone(),
	                divider: start.clone(),
	                dividerControlPoint1: control1.clone(),
	                dividerControlPoint2: control2.clone()
	            };
	        }

	        if (t >= 1) {
	            return {
	                startControlPoint1: control1.clone(),
	                startControlPoint2: control2.clone(),
	                divider: end.clone(),
	                dividerControlPoint1: end.clone(),
	                dividerControlPoint2: end.clone()
	            };
	        }

	        var midpoint1 = (new Line(start, control1)).pointAt(t);
	        var midpoint2 = (new Line(control1, control2)).pointAt(t);
	        var midpoint3 = (new Line(control2, end)).pointAt(t);

	        var subControl1 = (new Line(midpoint1, midpoint2)).pointAt(t);
	        var subControl2 = (new Line(midpoint2, midpoint3)).pointAt(t);

	        var divider = (new Line(subControl1, subControl2)).pointAt(t);

	        var output = {
	            startControlPoint1: midpoint1,
	            startControlPoint2: subControl1,
	            divider: divider,
	            dividerControlPoint1: subControl2,
	            dividerControlPoint2: midpoint3
	        };

	        return output;
	    },

	    // Returns a list of curves whose flattened length is better than `opt.precision`.
	    // That is, observed difference in length between recursions is less than 10^(-3) = 0.001 = 0.1%
	    // (Observed difference is not real precision, but close enough as long as special cases are covered)
	    // As a rule of thumb, increasing `precision` by 1 requires 2 more iterations (= levels of division operations)
	    // - Precision 0 (endpointDistance) - 0 iterations => total of 2^0 - 1 = 0 operations (1 subdivision)
	    // - Precision 1 (<10% error) - 2 iterations => total of 2^2 - 1 = 3 operations (4 subdivisions)
	    // - Precision 2 (<1% error) - 4 iterations => total of 2^4 - 1 = 15 operations requires 4 division operations on all elements (15 operations total) (16 subdivisions)
	    // - Precision 3 (<0.1% error) - 6 iterations => total of 2^6 - 1 = 63 operations - acceptable when drawing (64 subdivisions)
	    // - Precision 4 (<0.01% error) - 8 iterations => total of 2^8 - 1 = 255 operations - high resolution, can be used to interpolate `t` (256 subdivisions)
	    // (Variation of 1 recursion worse or better is possible depending on the curve, doubling/halving the number of operations accordingly)
	    getSubdivisions: function(opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        // not using opt.subdivisions
	        // not using localOpt

	        var start = this.start;
	        var control1 = this.controlPoint1;
	        var control2 = this.controlPoint2;
	        var end = this.end;

	        var subdivisions = [new Curve(start, control1, control2, end)];
	        if (precision === 0) { return subdivisions; }

	        // special case #1: point-like curves
	        // - no need to calculate subdivisions, they would all be identical
	        var isPoint = !this.isDifferentiable();
	        if (isPoint) { return subdivisions; }

	        var previousLength = this.endpointDistance();

	        var precisionRatio = pow$3(10, -precision);

	        // special case #2: sine-like curves may have the same observed length in iteration 0 and 1 - skip iteration 1
	        // - not a problem for further iterations because cubic curves cannot have more than two local extrema
	        // - (i.e. cubic curves cannot intersect the baseline more than once)
	        // - therefore starting from iteration = 2 ensures that subsequent iterations do not produce sampling with equal length
	        // - (unless it's a straight-line curve, see below)
	        var minIterations = 2; // = 2*1

	        // special case #3: straight-line curves have the same observed length in all iterations
	        // - this causes observed precision ratio to always be 0 (= lower than `precisionRatio`, which is our exit condition)
	        // - we enforce the expected number of iterations = 2 * precision
	        var isLine = ((control1.cross(start, end) === 0) && (control2.cross(start, end) === 0));
	        if (isLine) {
	            minIterations = (2 * precision);
	        }

	        // recursively divide curve at `t = 0.5`
	        // until we reach `minIterations`
	        // and until the difference between observed length at subsequent iterations is lower than `precision`
	        var iteration = 0;
	        while (true) {
	            iteration += 1;

	            // divide all subdivisions
	            var newSubdivisions = [];
	            var numSubdivisions = subdivisions.length;
	            for (var i = 0; i < numSubdivisions; i++) {

	                var currentSubdivision = subdivisions[i];
	                var divided = currentSubdivision.divide(0.5); // dividing at t = 0.5 (not at middle length!)
	                newSubdivisions.push(divided[0], divided[1]);
	            }

	            // measure new length
	            var length = 0;
	            var numNewSubdivisions = newSubdivisions.length;
	            for (var j = 0; j < numNewSubdivisions; j++) {

	                var currentNewSubdivision = newSubdivisions[j];
	                length += currentNewSubdivision.endpointDistance();
	            }

	            // check if we have reached minimum number of iterations
	            if (iteration >= minIterations) {

	                // check if we have reached required observed precision
	                var observedPrecisionRatio = ((length !== 0) ? ((length - previousLength) / length) : 0);
	                if (observedPrecisionRatio < precisionRatio) {
	                    return newSubdivisions;
	                }
	            }

	            // otherwise, set up for next iteration
	            subdivisions = newSubdivisions;
	            previousLength = length;
	        }
	    },

	    isDifferentiable: function() {

	        var start = this.start;
	        var control1 = this.controlPoint1;
	        var control2 = this.controlPoint2;
	        var end = this.end;

	        return !(start.equals(control1) && control1.equals(control2) && control2.equals(end));
	    },

	    // Returns flattened length of the curve with precision better than `opt.precision`; or using `opt.subdivisions` provided.
	    length: function(opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        // not using localOpt

	        var length = 0;
	        var n = subdivisions.length;
	        for (var i = 0; i < n; i++) {

	            var currentSubdivision = subdivisions[i];
	            length += currentSubdivision.endpointDistance();
	        }

	        return length;
	    },

	    // Returns distance along the curve up to `t` with precision better than requested `opt.precision`. (Not using `opt.subdivisions`.)
	    lengthAtT: function(t, opt) {

	        if (t <= 0) { return 0; }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        // not using opt.subdivisions
	        // not using localOpt

	        var subCurve = this.divide(t)[0];
	        var subCurveLength = subCurve.length({ precision: precision });

	        return subCurveLength;
	    },

	    // Returns point at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
	    // Mirrors Line.pointAt() function.
	    // For a function that tracks `t`, use Curve.pointAtT().
	    pointAt: function(ratio, opt) {

	        if (ratio <= 0) { return this.start.clone(); }
	        if (ratio >= 1) { return this.end.clone(); }

	        var t = this.tAt(ratio, opt);

	        return this.pointAtT(t);
	    },

	    // Returns point at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
	    pointAtLength: function(length, opt) {

	        var t = this.tAtLength(length, opt);

	        return this.pointAtT(t);
	    },

	    // Returns the point at provided `t` between 0 and 1.
	    // `t` does not track distance along curve as it does in Line objects.
	    // Non-linear relationship, speeds up and slows down as curve warps!
	    // For linear length-based solution, use Curve.pointAt().
	    pointAtT: function(t) {

	        if (t <= 0) { return this.start.clone(); }
	        if (t >= 1) { return this.end.clone(); }

	        return this.getSkeletonPoints(t).divider;
	    },

	    // Default precision
	    PRECISION: 3,

	    round: function(precision) {

	        this.start.round(precision);
	        this.controlPoint1.round(precision);
	        this.controlPoint2.round(precision);
	        this.end.round(precision);
	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        this.start.scale(sx, sy, origin);
	        this.controlPoint1.scale(sx, sy, origin);
	        this.controlPoint2.scale(sx, sy, origin);
	        this.end.scale(sx, sy, origin);
	        return this;
	    },

	    // Returns a tangent line at requested `ratio` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided.
	    tangentAt: function(ratio, opt) {

	        if (!this.isDifferentiable()) { return null; }

	        if (ratio < 0) { ratio = 0; }
	        else if (ratio > 1) { ratio = 1; }

	        var t = this.tAt(ratio, opt);

	        return this.tangentAtT(t);
	    },

	    // Returns a tangent line at requested `length` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided.
	    tangentAtLength: function(length, opt) {

	        if (!this.isDifferentiable()) { return null; }

	        var t = this.tAtLength(length, opt);

	        return this.tangentAtT(t);
	    },

	    // Returns a tangent line at requested `t`.
	    tangentAtT: function(t) {

	        if (!this.isDifferentiable()) { return null; }

	        if (t < 0) { t = 0; }
	        else if (t > 1) { t = 1; }

	        var skeletonPoints = this.getSkeletonPoints(t);

	        var p1 = skeletonPoints.startControlPoint2;
	        var p2 = skeletonPoints.dividerControlPoint1;

	        var tangentStart = skeletonPoints.divider;

	        var tangentLine = new Line(p1, p2);
	        tangentLine.translate(tangentStart.x - p1.x, tangentStart.y - p1.y); // move so that tangent line starts at the point requested

	        return tangentLine;
	    },

	    // Returns `t` at requested `ratio` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
	    tAt: function(ratio, opt) {

	        if (ratio <= 0) { return 0; }
	        if (ratio >= 1) { return 1; }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        var localOpt = { precision: precision, subdivisions: subdivisions };

	        var curveLength = this.length(localOpt);
	        var length = curveLength * ratio;

	        return this.tAtLength(length, localOpt);
	    },

	    // Returns `t` at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
	    // Uses `precision` to approximate length within `precision` (always underestimates)
	    // Then uses a binary search to find the `t` of a subdivision endpoint that is close (within `precision`) to the `length`, if the curve was as long as approximated
	    // As a rule of thumb, increasing `precision` by 1 causes the algorithm to go 2^(precision - 1) deeper
	    // - Precision 0 (chooses one of the two endpoints) - 0 levels
	    // - Precision 1 (chooses one of 5 points, <10% error) - 1 level
	    // - Precision 2 (<1% error) - 3 levels
	    // - Precision 3 (<0.1% error) - 7 levels
	    // - Precision 4 (<0.01% error) - 15 levels
	    tAtLength: function(length, opt) {

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        var localOpt = { precision: precision, subdivisions: subdivisions };

	        // identify the subdivision that contains the point at requested `length`:
	        var investigatedSubdivision;
	        var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced
	        var investigatedSubdivisionEndT;
	        //var baseline; // straightened version of subdivision to investigate
	        //var baselinePoint; // point on the baseline that is the requested distance away from start
	        var baselinePointDistFromStart; // distance of baselinePoint from start of baseline
	        var baselinePointDistFromEnd; // distance of baselinePoint from end of baseline
	        var l = 0; // length so far
	        var n = subdivisions.length;
	        var subdivisionSize = 1 / n;
	        for (var i = 0; i < n; i++) {
	            var index = (fromStart ? i : (n - 1 - i));

	            var currentSubdivision = subdivisions[i];
	            var d = currentSubdivision.endpointDistance(); // length of current subdivision

	            if (length <= (l + d)) {
	                investigatedSubdivision = currentSubdivision;

	                investigatedSubdivisionStartT = index * subdivisionSize;
	                investigatedSubdivisionEndT = (index + 1) * subdivisionSize;

	                baselinePointDistFromStart = (fromStart ? (length - l) : ((d + l) - length));
	                baselinePointDistFromEnd = (fromStart ? ((d + l) - length) : (length - l));

	                break;
	            }

	            l += d;
	        }

	        if (!investigatedSubdivision) { return (fromStart ? 1 : 0); } // length requested is out of range - return maximum t
	        // note that precision affects what length is recorded
	        // (imprecise measurements underestimate length by up to 10^(-precision) of the precise length)
	        // e.g. at precision 1, the length may be underestimated by up to 10% and cause this function to return 1

	        var curveLength = this.length(localOpt);

	        var precisionRatio = pow$3(10, -precision);

	        // recursively divide investigated subdivision:
	        // until distance between baselinePoint and closest path endpoint is within 10^(-precision)
	        // then return the closest endpoint of that final subdivision
	        while (true) {

	            // check if we have reached required observed precision
	            var observedPrecisionRatio;

	            observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromStart / curveLength) : 0);
	            if (observedPrecisionRatio < precisionRatio) { return investigatedSubdivisionStartT; }
	            observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromEnd / curveLength) : 0);
	            if (observedPrecisionRatio < precisionRatio) { return investigatedSubdivisionEndT; }

	            // otherwise, set up for next iteration
	            var newBaselinePointDistFromStart;
	            var newBaselinePointDistFromEnd;

	            var divided = investigatedSubdivision.divide(0.5);
	            subdivisionSize /= 2;

	            var baseline1Length = divided[0].endpointDistance();
	            var baseline2Length = divided[1].endpointDistance();

	            if (baselinePointDistFromStart <= baseline1Length) { // point at requested length is inside divided[0]
	                investigatedSubdivision = divided[0];

	                investigatedSubdivisionEndT -= subdivisionSize; // sudivisionSize was already halved

	                newBaselinePointDistFromStart = baselinePointDistFromStart;
	                newBaselinePointDistFromEnd = baseline1Length - newBaselinePointDistFromStart;

	            } else { // point at requested length is inside divided[1]
	                investigatedSubdivision = divided[1];

	                investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved

	                newBaselinePointDistFromStart = baselinePointDistFromStart - baseline1Length;
	                newBaselinePointDistFromEnd = baseline2Length - newBaselinePointDistFromStart;
	            }

	            baselinePointDistFromStart = newBaselinePointDistFromStart;
	            baselinePointDistFromEnd = newBaselinePointDistFromEnd;
	        }
	    },

	    // Returns an array of points that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided.
	    // Flattened length is no more than 10^(-precision) away from real curve length.
	    toPoints: function(opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call
	        var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions;
	        // not using localOpt

	        var points = [subdivisions[0].start.clone()];
	        var n = subdivisions.length;
	        for (var i = 0; i < n; i++) {

	            var currentSubdivision = subdivisions[i];
	            points.push(currentSubdivision.end.clone());
	        }

	        return points;
	    },

	    // Returns a polyline that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided.
	    // Flattened length is no more than 10^(-precision) away from real curve length.
	    toPolyline: function(opt) {

	        return new Polyline(this.toPoints(opt));
	    },

	    toString: function() {

	        return this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end;
	    },

	    translate: function(tx, ty) {

	        this.start.translate(tx, ty);
	        this.controlPoint1.translate(tx, ty);
	        this.controlPoint2.translate(tx, ty);
	        this.end.translate(tx, ty);
	        return this;
	    }
	};

	Curve.prototype.divide = Curve.prototype.divideAtT;

	// Local helper function.
	// Add properties from arguments on top of properties from `obj`.
	// This allows for rudimentary inheritance.
	// - The `obj` argument acts as parent.
	// - This function creates a new object that inherits all `obj` properties and adds/replaces those that are present in arguments.
	// - A high-level example: calling `extend(Vehicle, Car)` would be akin to declaring `class Car extends Vehicle`.
	function extend(obj) {
	    var arguments$1 = arguments;

	    // In JavaScript, the combination of a constructor function (e.g. `g.Line = function(...) {...}`) and prototype (e.g. `g.Line.prototype = {...}) is akin to a C++ class.
	    // - When inheritance is not necessary, we can leave it at that. (This would be akin to calling extend with only `obj`.)
	    // - But, what if we wanted the `g.Line` quasiclass to inherit from another quasiclass (let's call it `g.GeometryObject`) in JavaScript?
	    // - First, realize that both of those quasiclasses would still have their own separate constructor function.
	    // - So what we are actually saying is that we want the `g.Line` prototype to inherit from `g.GeometryObject` prototype.
	    // - This method provides a way to do exactly that.
	    // - It copies parent prototype's properties, then adds extra ones from child prototype/overrides parent prototype properties with child prototype properties.
	    // - Therefore, to continue with the example above:
	    //   - `g.Line.prototype = extend(g.GeometryObject.prototype, linePrototype)`
	    //   - Where `linePrototype` is a properties object that looks just like `g.Line.prototype` does right now.
	    //   - Then, `g.Line` would allow the programmer to access to all methods currently in `g.Line.Prototype`, plus any non-overridden methods from `g.GeometryObject.prototype`.
	    //   - In that aspect, `g.GeometryObject` would then act like the parent of `g.Line`.
	    // - Multiple inheritance is also possible, if multiple arguments are provided.
	    // - What if we wanted to add another level of abstraction between `g.GeometryObject` and `g.Line` (let's call it `g.LinearObject`)?
	    //   - `g.Line.prototype = extend(g.GeometryObject.prototype, g.LinearObject.prototype, linePrototype)`
	    //   - The ancestors are applied in order of appearance.
	    //   - That means that `g.Line` would have inherited from `g.LinearObject` that would have inherited from `g.GeometryObject`.
	    //   - Any number of ancestors may be provided.
	    // - Note that neither `obj` nor any of the arguments need to actually be prototypes of any JavaScript quasiclass, that was just a simplified explanation.
	    // - We can create a new object composed from the properties of any number of other objects (since they do not have a constructor, we can think of those as interfaces).
	    //   - `extend({ a: 1, b: 2 }, { b: 10, c: 20 }, { c: 100, d: 200 })` gives `{ a: 1, b: 10, c: 100, d: 200 }`.
	    //   - Basically, with this function, we can emulate the `extends` keyword as well as the `implements` keyword.
	    // - Therefore, both of the following are valid:
	    //   - `Lineto.prototype = extend(Line.prototype, segmentPrototype, linetoPrototype)`
	    //   - `Moveto.prototype = extend(segmentPrototype, movetoPrototype)`

	    var i;
	    var n;

	    var args = [];
	    n = arguments.length;
	    for (i = 1; i < n; i++) { // skip over obj
	        args.push(arguments$1[i]);
	    }

	    if (!obj) { throw new Error('Missing a parent object.'); }
	    var child = Object.create(obj);

	    n = args.length;
	    for (i = 0; i < n; i++) {

	        var src = args[i];

	        var inheritedProperty;
	        var key;
	        for (key in src) {

	            if (src.hasOwnProperty(key)) {
	                delete child[key]; // delete property inherited from parent
	                inheritedProperty = Object.getOwnPropertyDescriptor(src, key); // get new definition of property from src
	                Object.defineProperty(child, key, inheritedProperty); // re-add property with new definition (includes getter/setter methods)
	            }
	        }
	    }

	    return child;
	}

	// Accepts path data string, array of segments, array of Curves and/or Lines, or a Polyline.
	var Path = function(arg) {

	    if (!(this instanceof Path)) {
	        return new Path(arg);
	    }

	    if (typeof arg === 'string') { // create from a path data string
	        return new Path.parse(arg);
	    }

	    this.segments = [];

	    var i;
	    var n;

	    if (!arg) {
	        // don't do anything

	    } else if (Array.isArray(arg) && arg.length !== 0) { // if arg is a non-empty array
	        // flatten one level deep
	        // so we can chain arbitrary Path.createSegment results
	        arg = arg.reduce(function(acc, val) {
	            return acc.concat(val);
	        }, []);

	        n = arg.length;
	        if (arg[0].isSegment) { // create from an array of segments
	            for (i = 0; i < n; i++) {

	                var segment = arg[i];

	                this.appendSegment(segment);
	            }

	        } else { // create from an array of Curves and/or Lines
	            var previousObj = null;
	            for (i = 0; i < n; i++) {

	                var obj = arg[i];

	                if (!((obj instanceof Line) || (obj instanceof Curve))) {
	                    throw new Error('Cannot construct a path segment from the provided object.');
	                }

	                if (i === 0) { this.appendSegment(Path.createSegment('M', obj.start)); }

	                // if objects do not link up, moveto segments are inserted to cover the gaps
	                if (previousObj && !previousObj.end.equals(obj.start)) { this.appendSegment(Path.createSegment('M', obj.start)); }

	                if (obj instanceof Line) {
	                    this.appendSegment(Path.createSegment('L', obj.end));

	                } else if (obj instanceof Curve) {
	                    this.appendSegment(Path.createSegment('C', obj.controlPoint1, obj.controlPoint2, obj.end));
	                }

	                previousObj = obj;
	            }
	        }

	    } else if (arg.isSegment) { // create from a single segment
	        this.appendSegment(arg);

	    } else if (arg instanceof Line) { // create from a single Line
	        this.appendSegment(Path.createSegment('M', arg.start));
	        this.appendSegment(Path.createSegment('L', arg.end));

	    } else if (arg instanceof Curve) { // create from a single Curve
	        this.appendSegment(Path.createSegment('M', arg.start));
	        this.appendSegment(Path.createSegment('C', arg.controlPoint1, arg.controlPoint2, arg.end));

	    } else if (arg instanceof Polyline) { // create from a Polyline
	        if (!(arg.points && (arg.points.length !== 0))) { return; } // if Polyline has no points, leave Path empty

	        n = arg.points.length;
	        for (i = 0; i < n; i++) {

	            var point = arg.points[i];

	            if (i === 0) { this.appendSegment(Path.createSegment('M', point)); }
	            else { this.appendSegment(Path.createSegment('L', point)); }
	        }

	    } else { // unknown object
	        throw new Error('Cannot construct a path from the provided object.');
	    }
	};

	// More permissive than V.normalizePathData and Path.prototype.serialize.
	// Allows path data strings that do not start with a Moveto command (unlike SVG specification).
	// Does not require spaces between elements; commas are allowed, separators may be omitted when unambiguous (e.g. 'ZM10,10', 'L1.6.8', 'M100-200').
	// Allows for command argument chaining.
	// Throws an error if wrong number of arguments is provided with a command.
	// Throws an error if an unrecognized path command is provided (according to Path.segmentTypes). Only a subset of SVG commands is currently supported (L, C, M, Z).
	Path.parse = function(pathData) {

	    if (!pathData) { return new Path(); }

	    var path = new Path();

	    var commandRe = /(?:[a-zA-Z] *)(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)? *,? *)|(?:-?\.\d+ *,? *))+|(?:[a-zA-Z] *)(?! |\d|-|\.)/g;
	    var commands = pathData.match(commandRe);

	    var numCommands = commands.length;
	    for (var i = 0; i < numCommands; i++) {

	        var command = commands[i];
	        var argRe = /(?:[a-zA-Z])|(?:(?:-?\d+(?:\.\d+)?(?:e[-+]?\d+)?))|(?:(?:-?\.\d+))/g;
	        var args = command.match(argRe);

	        var segment = Path.createSegment.apply(this, args); // args = [type, coordinate1, coordinate2...]
	        path.appendSegment(segment);
	    }

	    return path;
	};

	// Create a segment or an array of segments.
	// Accepts unlimited points/coords arguments after `type`.
	Path.createSegment = function(type) {
	    var arguments$1 = arguments;


	    if (!type) { throw new Error('Type must be provided.'); }

	    var segmentConstructor = Path.segmentTypes[type];
	    if (!segmentConstructor) { throw new Error(type + ' is not a recognized path segment type.'); }

	    var args = [];
	    var n = arguments.length;
	    for (var i = 1; i < n; i++) { // do not add first element (`type`) to args array
	        args.push(arguments$1[i]);
	    }

	    return applyToNew(segmentConstructor, args);
	};

	Path.prototype = {

	    type: types.Path,

	    // Accepts one segment or an array of segments as argument.
	    // Throws an error if argument is not a segment or an array of segments.
	    appendSegment: function(arg) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        // works even if path has no segments

	        var currentSegment;

	        var previousSegment = ((numSegments !== 0) ? segments[numSegments - 1] : null); // if we are appending to an empty path, previousSegment is null
	        var nextSegment = null;

	        if (!Array.isArray(arg)) { // arg is a segment
	            if (!arg || !arg.isSegment) { throw new Error('Segment required.'); }

	            currentSegment = this.prepareSegment(arg, previousSegment, nextSegment);
	            segments.push(currentSegment);

	        } else { // arg is an array of segments
	            // flatten one level deep
	            // so we can chain arbitrary Path.createSegment results
	            arg = arg.reduce(function(acc, val) {
	                return acc.concat(val);
	            }, []);

	            if (!arg[0].isSegment) { throw new Error('Segments required.'); }

	            var n = arg.length;
	            for (var i = 0; i < n; i++) {

	                var currentArg = arg[i];
	                currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment);
	                segments.push(currentSegment);
	                previousSegment = currentSegment;
	            }
	        }
	    },

	    // Returns the bbox of the path.
	    // If path has no segments, returns null.
	    // If path has only invisible segments, returns bbox of the end point of last segment.
	    bbox: function() {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        var bbox;
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            if (segment.isVisible) {
	                var segmentBBox = segment.bbox();
	                bbox = bbox ? bbox.union(segmentBBox) : segmentBBox;
	            }
	        }

	        if (bbox) { return bbox; }

	        // if the path has only invisible elements, return end point of last segment
	        var lastSegment = segments[numSegments - 1];
	        return new Rect(lastSegment.end.x, lastSegment.end.y, 0, 0);
	    },

	    // Returns a new path that is a clone of this path.
	    clone: function() {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        // works even if path has no segments

	        var path = new Path();
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i].clone();
	            path.appendSegment(segment);
	        }

	        return path;
	    },

	    closestPoint: function(p, opt) {

	        var t = this.closestPointT(p, opt);
	        if (!t) { return null; }

	        return this.pointAtT(t);
	    },

	    closestPointLength: function(p, opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };

	        var t = this.closestPointT(p, localOpt);
	        if (!t) { return 0; }

	        return this.lengthAtT(t, localOpt);
	    },

	    closestPointNormalizedLength: function(p, opt) {

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };

	        var cpLength = this.closestPointLength(p, localOpt);
	        if (cpLength === 0) { return 0; } // shortcut

	        var length = this.length(localOpt);
	        if (length === 0) { return 0; } // prevents division by zero

	        return cpLength / length;
	    },

	    // Private function.
	    closestPointT: function(p, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var closestPointT;
	        var minSquaredDistance = Infinity;
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            var subdivisions = segmentSubdivisions[i];

	            if (segment.isVisible) {
	                var segmentClosestPointT = segment.closestPointT(p, {
	                    precision: precision,
	                    subdivisions: subdivisions
	                });
	                var segmentClosestPoint = segment.pointAtT(segmentClosestPointT);
	                var squaredDistance = (new Line(segmentClosestPoint, p)).squaredLength();

	                if (squaredDistance < minSquaredDistance) {
	                    closestPointT = { segmentIndex: i, value: segmentClosestPointT };
	                    minSquaredDistance = squaredDistance;
	                }
	            }
	        }

	        if (closestPointT) { return closestPointT; }

	        // if no visible segment, return end of last segment
	        return { segmentIndex: numSegments - 1, value: 1 };
	    },

	    closestPointTangent: function(p, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var closestPointTangent;
	        var minSquaredDistance = Infinity;
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            var subdivisions = segmentSubdivisions[i];

	            if (segment.isDifferentiable()) {
	                var segmentClosestPointT = segment.closestPointT(p, {
	                    precision: precision,
	                    subdivisions: subdivisions
	                });
	                var segmentClosestPoint = segment.pointAtT(segmentClosestPointT);
	                var squaredDistance = (new Line(segmentClosestPoint, p)).squaredLength();

	                if (squaredDistance < minSquaredDistance) {
	                    closestPointTangent = segment.tangentAtT(segmentClosestPointT);
	                    minSquaredDistance = squaredDistance;
	                }
	            }
	        }

	        if (closestPointTangent) { return closestPointTangent; }

	        // if no valid segment, return null
	        return null;
	    },

	    // Returns `true` if the area surrounded by the path contains the point `p`.
	    // Implements the even-odd algorithm (self-intersections are "outside").
	    // Closes open paths (always imagines a final closing segment).
	    // Precision may be adjusted by passing an `opt` object.
	    containsPoint: function(p, opt) {

	        var polylines = this.toPolylines(opt);
	        if (!polylines) { return false; } // shortcut (this path has no polylines)

	        var numPolylines = polylines.length;

	        // how many component polylines does `p` lie within?
	        var numIntersections = 0;
	        for (var i = 0; i < numPolylines; i++) {
	            var polyline = polylines[i];
	            if (polyline.containsPoint(p)) {
	                // `p` lies within this polyline
	                numIntersections++;
	            }
	        }

	        // returns `true` for odd numbers of intersections (even-odd algorithm)
	        return ((numIntersections % 2) === 1);
	    },

	    // Divides the path into two at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided.
	    divideAt: function(ratio, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        if (ratio < 0) { ratio = 0; }
	        if (ratio > 1) { ratio = 1; }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };

	        var pathLength = this.length(localOpt);
	        var length = pathLength * ratio;

	        return this.divideAtLength(length, localOpt);
	    },

	    // Divides the path into two at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided.
	    divideAtLength: function(length, opt) {

	        var numSegments = this.segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var i;
	        var segment;

	        // identify the segment to divide:

	        var l = 0; // length so far
	        var divided;
	        var dividedSegmentIndex;
	        var lastValidSegment; // visible AND differentiable
	        var lastValidSegmentIndex;
	        var t;
	        for (i = 0; i < numSegments; i++) {
	            var index = (fromStart ? i : (numSegments - 1 - i));

	            segment = this.getSegment(index);
	            var subdivisions = segmentSubdivisions[index];
	            var d = segment.length({ precision: precision, subdivisions: subdivisions });

	            if (segment.isDifferentiable()) { // segment is not just a point
	                lastValidSegment = segment;
	                lastValidSegmentIndex = index;

	                if (length <= (l + d)) {
	                    dividedSegmentIndex = index;
	                    divided = segment.divideAtLength(((fromStart ? 1 : -1) * (length - l)), {
	                        precision: precision,
	                        subdivisions: subdivisions
	                    });
	                    break;
	                }
	            }

	            l += d;
	        }

	        if (!lastValidSegment) { // no valid segment found
	            return null;
	        }

	        // else: the path contains at least one valid segment

	        if (!divided) { // the desired length is greater than the length of the path
	            dividedSegmentIndex = lastValidSegmentIndex;
	            t = (fromStart ? 1 : 0);
	            divided = lastValidSegment.divideAtT(t);
	        }

	        // create a copy of this path and replace the identified segment with its two divided parts:

	        var pathCopy = this.clone();
	        pathCopy.replaceSegment(dividedSegmentIndex, divided);

	        var divisionStartIndex = dividedSegmentIndex;
	        var divisionMidIndex = dividedSegmentIndex + 1;
	        var divisionEndIndex = dividedSegmentIndex + 2;

	        // do not insert the part if it looks like a point
	        if (!divided[0].isDifferentiable()) {
	            pathCopy.removeSegment(divisionStartIndex);
	            divisionMidIndex -= 1;
	            divisionEndIndex -= 1;
	        }

	        // insert a Moveto segment to ensure secondPath will be valid:
	        var movetoEnd = pathCopy.getSegment(divisionMidIndex).start;
	        pathCopy.insertSegment(divisionMidIndex, Path.createSegment('M', movetoEnd));
	        divisionEndIndex += 1;

	        // do not insert the part if it looks like a point
	        if (!divided[1].isDifferentiable()) {
	            pathCopy.removeSegment(divisionEndIndex - 1);
	            divisionEndIndex -= 1;
	        }

	        // ensure that Closepath segments in secondPath will be assigned correct subpathStartSegment:

	        var secondPathSegmentIndexConversion = divisionEndIndex - divisionStartIndex - 1;
	        for (i = divisionEndIndex; i < pathCopy.segments.length; i++) {

	            var originalSegment = this.getSegment(i - secondPathSegmentIndexConversion);
	            segment = pathCopy.getSegment(i);

	            if ((segment.type === 'Z') && !originalSegment.subpathStartSegment.end.equals(segment.subpathStartSegment.end)) {
	                // pathCopy segment's subpathStartSegment is different from original segment's one
	                // convert this Closepath segment to a Lineto and replace it in pathCopy
	                var convertedSegment = Path.createSegment('L', originalSegment.end);
	                pathCopy.replaceSegment(i, convertedSegment);
	            }
	        }

	        // distribute pathCopy segments into two paths and return those:

	        var firstPath = new Path(pathCopy.segments.slice(0, divisionMidIndex));
	        var secondPath = new Path(pathCopy.segments.slice(divisionMidIndex));

	        return [firstPath, secondPath];
	    },

	    // Checks whether two paths are exactly the same.
	    // If `p` is undefined or null, returns false.
	    equals: function(p) {

	        if (!p) { return false; }

	        var segments = this.segments;
	        var otherSegments = p.segments;

	        var numSegments = segments.length;
	        if (otherSegments.length !== numSegments) { return false; } // if the two paths have different number of segments, they cannot be equal

	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            var otherSegment = otherSegments[i];

	            // as soon as an inequality is found in segments, return false
	            if ((segment.type !== otherSegment.type) || (!segment.equals(otherSegment))) { return false; }
	        }

	        // if no inequality found in segments, return true
	        return true;
	    },

	    // Accepts negative indices.
	    // Throws an error if path has no segments.
	    // Throws an error if index is out of range.
	    getSegment: function(index) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { throw new Error('Path has no segments.'); }

	        if (index < 0) { index = numSegments + index; } // convert negative indices to positive
	        if (index >= numSegments || index < 0) { throw new Error('Index out of range.'); }

	        return segments[index];
	    },

	    // Returns an array of segment subdivisions, with precision better than requested `opt.precision`.
	    getSegmentSubdivisions: function(opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        // works even if path has no segments

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        // not using opt.segmentSubdivisions
	        // not using localOpt

	        var segmentSubdivisions = [];
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            var subdivisions = segment.getSubdivisions({ precision: precision });
	            segmentSubdivisions.push(subdivisions);
	        }

	        return segmentSubdivisions;
	    },

	    // Returns an array of subpaths of this path.
	    // Invalid paths are validated first.
	    // Returns `[]` if path has no segments.
	    getSubpaths: function() {

	        var validatedPath = this.clone().validate();

	        var segments = validatedPath.segments;
	        var numSegments = segments.length;

	        var subpaths = [];
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            if (segment.isSubpathStart) {
	                // we encountered a subpath start segment
	                // create a new path for segment, and push it to list of subpaths
	                subpaths.push(new Path(segment));

	            } else {
	                // append current segment to the last subpath
	                subpaths[subpaths.length - 1].appendSegment(segment);
	            }
	        }

	        return subpaths;
	    },

	    // Insert `arg` at given `index`.
	    // `index = 0` means insert at the beginning.
	    // `index = segments.length` means insert at the end.
	    // Accepts negative indices, from `-1` to `-(segments.length + 1)`.
	    // Accepts one segment or an array of segments as argument.
	    // Throws an error if index is out of range.
	    // Throws an error if argument is not a segment or an array of segments.
	    insertSegment: function(index, arg) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        // works even if path has no segments

	        // note that these are incremented compared to getSegments()
	        // we can insert after last element (note that this changes the meaning of index -1)
	        if (index < 0) { index = numSegments + index + 1; } // convert negative indices to positive
	        if (index > numSegments || index < 0) { throw new Error('Index out of range.'); }

	        var currentSegment;

	        var previousSegment = null;
	        var nextSegment = null;

	        if (numSegments !== 0) {
	            if (index >= 1) {
	                previousSegment = segments[index - 1];
	                nextSegment = previousSegment.nextSegment; // if we are inserting at end, nextSegment is null

	            } else { // if index === 0
	                // previousSegment is null
	                nextSegment = segments[0];
	            }
	        }

	        if (!Array.isArray(arg)) {
	            if (!arg || !arg.isSegment) { throw new Error('Segment required.'); }

	            currentSegment = this.prepareSegment(arg, previousSegment, nextSegment);
	            segments.splice(index, 0, currentSegment);

	        } else {
	            // flatten one level deep
	            // so we can chain arbitrary Path.createSegment results
	            arg = arg.reduce(function(acc, val) {
	                return acc.concat(val);
	            }, []);

	            if (!arg[0].isSegment) { throw new Error('Segments required.'); }

	            var n = arg.length;
	            for (var i = 0; i < n; i++) {

	                var currentArg = arg[i];
	                currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment);
	                segments.splice((index + i), 0, currentSegment); // incrementing index to insert subsequent segments after inserted segments
	                previousSegment = currentSegment;
	            }
	        }
	    },

	    intersectionWithLine: function(line, opt) {

	        var intersection = null;
	        var polylines = this.toPolylines(opt);
	        if (!polylines) { return null; }
	        for (var i = 0, n = polylines.length; i < n; i++) {
	            var polyline = polylines[i];
	            var polylineIntersection = line.intersect(polyline);
	            if (polylineIntersection) {
	                intersection || (intersection = []);
	                if (Array.isArray(polylineIntersection)) {
	                    Array.prototype.push.apply(intersection, polylineIntersection);
	                } else {
	                    intersection.push(polylineIntersection);
	                }
	            }
	        }

	        return intersection;
	    },

	    isDifferentiable: function() {

	        var segments = this.segments;
	        var numSegments = segments.length;

	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            // as soon as a differentiable segment is found in segments, return true
	            if (segment.isDifferentiable()) { return true; }
	        }

	        // if no differentiable segment is found in segments, return false
	        return false;
	    },

	    // Checks whether current path segments are valid.
	    // Note that d is allowed to be empty - should disable rendering of the path.
	    isValid: function() {

	        var segments = this.segments;
	        var isValid = (segments.length === 0) || (segments[0].type === 'M'); // either empty or first segment is a Moveto
	        return isValid;
	    },

	    // Returns length of the path, with precision better than requested `opt.precision`; or using `opt.segmentSubdivisions` provided.
	    // If path has no segments, returns 0.
	    length: function(opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return 0; } // if segments is an empty array

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSegmentSubdivisions() call
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var length = 0;
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            var subdivisions = segmentSubdivisions[i];
	            length += segment.length({ subdivisions: subdivisions });
	        }

	        return length;
	    },

	    // Private function.
	    lengthAtT: function(t, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return 0; } // if segments is an empty array

	        var segmentIndex = t.segmentIndex;
	        if (segmentIndex < 0) { return 0; } // regardless of t.value

	        var tValue = t.value;
	        if (segmentIndex >= numSegments) {
	            segmentIndex = numSegments - 1;
	            tValue = 1;
	        } else if (tValue < 0) { tValue = 0; }
	        else if (tValue > 1) { tValue = 1; }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var subdivisions;
	        var length = 0;
	        for (var i = 0; i < segmentIndex; i++) {

	            var segment = segments[i];
	            subdivisions = segmentSubdivisions[i];
	            length += segment.length({ precisison: precision, subdivisions: subdivisions });
	        }

	        segment = segments[segmentIndex];
	        subdivisions = segmentSubdivisions[segmentIndex];
	        length += segment.lengthAtT(tValue, { precisison: precision, subdivisions: subdivisions });

	        return length;
	    },

	    // Returns point at requested `ratio` between 0 and 1, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
	    pointAt: function(ratio, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        if (ratio <= 0) { return this.start.clone(); }
	        if (ratio >= 1) { return this.end.clone(); }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };

	        var pathLength = this.length(localOpt);
	        var length = pathLength * ratio;

	        return this.pointAtLength(length, localOpt);
	    },

	    // Returns point at requested `length`, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
	    // Accepts negative length.
	    pointAtLength: function(length, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        if (length === 0) { return this.start.clone(); }

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var lastVisibleSegment;
	        var l = 0; // length so far
	        for (var i = 0; i < numSegments; i++) {
	            var index = (fromStart ? i : (numSegments - 1 - i));

	            var segment = segments[index];
	            var subdivisions = segmentSubdivisions[index];
	            var d = segment.length({ precision: precision, subdivisions: subdivisions });

	            if (segment.isVisible) {
	                if (length <= (l + d)) {
	                    return segment.pointAtLength(((fromStart ? 1 : -1) * (length - l)), {
	                        precision: precision,
	                        subdivisions: subdivisions
	                    });
	                }

	                lastVisibleSegment = segment;
	            }

	            l += d;
	        }

	        // if length requested is higher than the length of the path, return last visible segment endpoint
	        if (lastVisibleSegment) { return (fromStart ? lastVisibleSegment.end : lastVisibleSegment.start); }

	        // if no visible segment, return last segment end point (no matter if fromStart or no)
	        var lastSegment = segments[numSegments - 1];
	        return lastSegment.end.clone();
	    },

	    // Private function.
	    pointAtT: function(t) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        var segmentIndex = t.segmentIndex;
	        if (segmentIndex < 0) { return segments[0].pointAtT(0); }
	        if (segmentIndex >= numSegments) { return segments[numSegments - 1].pointAtT(1); }

	        var tValue = t.value;
	        if (tValue < 0) { tValue = 0; }
	        else if (tValue > 1) { tValue = 1; }

	        return segments[segmentIndex].pointAtT(tValue);
	    },

	    // Default precision
	    PRECISION: 3,

	    // Helper method for adding segments.
	    prepareSegment: function(segment, previousSegment, nextSegment) {

	        // insert after previous segment and before previous segment's next segment
	        segment.previousSegment = previousSegment;
	        segment.nextSegment = nextSegment;
	        if (previousSegment) { previousSegment.nextSegment = segment; }
	        if (nextSegment) { nextSegment.previousSegment = segment; }

	        var updateSubpathStart = segment;
	        if (segment.isSubpathStart) {
	            segment.subpathStartSegment = segment; // assign self as subpath start segment
	            updateSubpathStart = nextSegment; // start updating from next segment
	        }

	        // assign previous segment's subpath start (or self if it is a subpath start) to subsequent segments
	        if (updateSubpathStart) { this.updateSubpathStartSegment(updateSubpathStart); }

	        return segment;
	    },

	    // Remove the segment at `index`.
	    // Accepts negative indices, from `-1` to `-segments.length`.
	    // Throws an error if path has no segments.
	    // Throws an error if index is out of range.
	    removeSegment: function(index) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { throw new Error('Path has no segments.'); }

	        if (index < 0) { index = numSegments + index; } // convert negative indices to positive
	        if (index >= numSegments || index < 0) { throw new Error('Index out of range.'); }

	        var removedSegment = segments.splice(index, 1)[0];
	        var previousSegment = removedSegment.previousSegment;
	        var nextSegment = removedSegment.nextSegment;

	        // link the previous and next segments together (if present)
	        if (previousSegment) { previousSegment.nextSegment = nextSegment; } // may be null
	        if (nextSegment) { nextSegment.previousSegment = previousSegment; } // may be null

	        // if removed segment used to start a subpath, update all subsequent segments until another subpath start segment is reached
	        if (removedSegment.isSubpathStart && nextSegment) { this.updateSubpathStartSegment(nextSegment); }
	    },

	    // Replace the segment at `index` with `arg`.
	    // Accepts negative indices, from `-1` to `-segments.length`.
	    // Accepts one segment or an array of segments as argument.
	    // Throws an error if path has no segments.
	    // Throws an error if index is out of range.
	    // Throws an error if argument is not a segment or an array of segments.
	    replaceSegment: function(index, arg) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { throw new Error('Path has no segments.'); }

	        if (index < 0) { index = numSegments + index; } // convert negative indices to positive
	        if (index >= numSegments || index < 0) { throw new Error('Index out of range.'); }

	        var currentSegment;

	        var replacedSegment = segments[index];
	        var previousSegment = replacedSegment.previousSegment;
	        var nextSegment = replacedSegment.nextSegment;

	        var updateSubpathStart = replacedSegment.isSubpathStart; // boolean: is an update of subpath starts necessary?

	        if (!Array.isArray(arg)) {
	            if (!arg || !arg.isSegment) { throw new Error('Segment required.'); }

	            currentSegment = this.prepareSegment(arg, previousSegment, nextSegment);
	            segments.splice(index, 1, currentSegment); // directly replace

	            if (updateSubpathStart && currentSegment.isSubpathStart) { updateSubpathStart = false; } // already updated by `prepareSegment`

	        } else {
	            // flatten one level deep
	            // so we can chain arbitrary Path.createSegment results
	            arg = arg.reduce(function(acc, val) {
	                return acc.concat(val);
	            }, []);

	            if (!arg[0].isSegment) { throw new Error('Segments required.'); }

	            segments.splice(index, 1);

	            var n = arg.length;
	            for (var i = 0; i < n; i++) {

	                var currentArg = arg[i];
	                currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment);
	                segments.splice((index + i), 0, currentSegment); // incrementing index to insert subsequent segments after inserted segments
	                previousSegment = currentSegment;

	                if (updateSubpathStart && currentSegment.isSubpathStart) { updateSubpathStart = false; } // already updated by `prepareSegment`
	            }
	        }

	        // if replaced segment used to start a subpath and no new subpath start was added, update all subsequent segments until another subpath start segment is reached
	        if (updateSubpathStart && nextSegment) { this.updateSubpathStartSegment(nextSegment); }
	    },

	    round: function(precision) {

	        var segments = this.segments;
	        var numSegments = segments.length;

	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            segment.round(precision);
	        }

	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        var segments = this.segments;
	        var numSegments = segments.length;

	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            segment.scale(sx, sy, origin);
	        }

	        return this;
	    },

	    segmentAt: function(ratio, opt) {

	        var index = this.segmentIndexAt(ratio, opt);
	        if (!index) { return null; }

	        return this.getSegment(index);
	    },

	    // Accepts negative length.
	    segmentAtLength: function(length, opt) {

	        var index = this.segmentIndexAtLength(length, opt);
	        if (!index) { return null; }

	        return this.getSegment(index);
	    },

	    segmentIndexAt: function(ratio, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        if (ratio < 0) { ratio = 0; }
	        if (ratio > 1) { ratio = 1; }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };

	        var pathLength = this.length(localOpt);
	        var length = pathLength * ratio;

	        return this.segmentIndexAtLength(length, localOpt);
	    },

	    // Accepts negative length.
	    segmentIndexAtLength: function(length, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var lastVisibleSegmentIndex = null;
	        var l = 0; // length so far
	        for (var i = 0; i < numSegments; i++) {
	            var index = (fromStart ? i : (numSegments - 1 - i));

	            var segment = segments[index];
	            var subdivisions = segmentSubdivisions[index];
	            var d = segment.length({ precision: precision, subdivisions: subdivisions });

	            if (segment.isVisible) {
	                if (length <= (l + d)) { return index; }
	                lastVisibleSegmentIndex = index;
	            }

	            l += d;
	        }

	        // if length requested is higher than the length of the path, return last visible segment index
	        // if no visible segment, return null
	        return lastVisibleSegmentIndex;
	    },

	    // Returns a string that can be used to reconstruct the path.
	    // Additional error checking compared to toString (must start with M segment).
	    serialize: function() {

	        if (!this.isValid()) { throw new Error('Invalid path segments.'); }

	        return this.toString();
	    },

	    // Returns tangent line at requested `ratio` between 0 and 1, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
	    tangentAt: function(ratio, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        if (ratio < 0) { ratio = 0; }
	        if (ratio > 1) { ratio = 1; }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions };

	        var pathLength = this.length(localOpt);
	        var length = pathLength * ratio;

	        return this.tangentAtLength(length, localOpt);
	    },

	    // Returns tangent line at requested `length`, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided.
	    // Accepts negative length.
	    tangentAtLength: function(length, opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        var fromStart = true;
	        if (length < 0) {
	            fromStart = false; // negative lengths mean start calculation from end point
	            length = -length; // absolute value
	        }

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;
	        // not using localOpt

	        var lastValidSegment; // visible AND differentiable (with a tangent)
	        var l = 0; // length so far
	        for (var i = 0; i < numSegments; i++) {
	            var index = (fromStart ? i : (numSegments - 1 - i));

	            var segment = segments[index];
	            var subdivisions = segmentSubdivisions[index];
	            var d = segment.length({ precision: precision, subdivisions: subdivisions });

	            if (segment.isDifferentiable()) {
	                if (length <= (l + d)) {
	                    return segment.tangentAtLength(((fromStart ? 1 : -1) * (length - l)), {
	                        precision: precision,
	                        subdivisions: subdivisions
	                    });
	                }

	                lastValidSegment = segment;
	            }

	            l += d;
	        }

	        // if length requested is higher than the length of the path, return tangent of endpoint of last valid segment
	        if (lastValidSegment) {
	            var t = (fromStart ? 1 : 0);
	            return lastValidSegment.tangentAtT(t);
	        }

	        // if no valid segment, return null
	        return null;
	    },

	    // Private function.
	    tangentAtT: function(t) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        var segmentIndex = t.segmentIndex;
	        if (segmentIndex < 0) { return segments[0].tangentAtT(0); }
	        if (segmentIndex >= numSegments) { return segments[numSegments - 1].tangentAtT(1); }

	        var tValue = t.value;
	        if (tValue < 0) { tValue = 0; }
	        else if (tValue > 1) { tValue = 1; }

	        return segments[segmentIndex].tangentAtT(tValue);
	    },

	    toPoints: function(opt) {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; } // if segments is an empty array

	        opt = opt || {};
	        var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision;
	        var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions;

	        var points = [];
	        var partialPoints = [];
	        for (var i = 0; i < numSegments; i++) {
	            var segment = segments[i];
	            if (segment.isVisible) {
	                var currentSegmentSubdivisions = segmentSubdivisions[i];
	                if (currentSegmentSubdivisions.length > 0) {
	                    var subdivisionPoints = currentSegmentSubdivisions.map(function(curve) {
	                        return curve.start;
	                    });
	                    Array.prototype.push.apply(partialPoints, subdivisionPoints);
	                } else {
	                    partialPoints.push(segment.start);
	                }
	            } else if (partialPoints.length > 0) {
	                partialPoints.push(segments[i - 1].end);
	                points.push(partialPoints);
	                partialPoints = [];
	            }
	        }

	        if (partialPoints.length > 0) {
	            partialPoints.push(this.end);
	            points.push(partialPoints);
	        }
	        return points;
	    },

	    toPolylines: function(opt) {

	        var polylines = [];
	        var points = this.toPoints(opt);
	        if (!points) { return null; }
	        for (var i = 0, n = points.length; i < n; i++) {
	            polylines.push(new Polyline(points[i]));
	        }

	        return polylines;
	    },

	    toString: function() {

	        var segments = this.segments;
	        var numSegments = segments.length;

	        var pathData = '';
	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            pathData += segment.serialize() + ' ';
	        }

	        return pathData.trim();
	    },

	    translate: function(tx, ty) {

	        var segments = this.segments;
	        var numSegments = segments.length;

	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            segment.translate(tx, ty);
	        }

	        return this;
	    },

	    // Helper method for updating subpath start of segments, starting with the one provided.
	    updateSubpathStartSegment: function(segment) {

	        var previousSegment = segment.previousSegment; // may be null
	        while (segment && !segment.isSubpathStart) {

	            // assign previous segment's subpath start segment to this segment
	            if (previousSegment) { segment.subpathStartSegment = previousSegment.subpathStartSegment; } // may be null
	            else { segment.subpathStartSegment = null; } // if segment had no previous segment, assign null - creates an invalid path!

	            previousSegment = segment;
	            segment = segment.nextSegment; // move on to the segment after etc.
	        }
	    },

	    // If the path is not valid, insert M 0 0 at the beginning.
	    // Path with no segments is considered valid, so nothing is inserted.
	    validate: function() {

	        if (!this.isValid()) { this.insertSegment(0, Path.createSegment('M', 0, 0)); }
	        return this;
	    }
	};

	Object.defineProperty(Path.prototype, 'start', {
	    // Getter for the first visible endpoint of the path.

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; }

	        for (var i = 0; i < numSegments; i++) {

	            var segment = segments[i];
	            if (segment.isVisible) { return segment.start; }
	        }

	        // if no visible segment, return last segment end point
	        return segments[numSegments - 1].end;
	    }
	});

	Object.defineProperty(Path.prototype, 'end', {
	    // Getter for the last visible endpoint of the path.

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        var segments = this.segments;
	        var numSegments = segments.length;
	        if (numSegments === 0) { return null; }

	        for (var i = numSegments - 1; i >= 0; i--) {

	            var segment = segments[i];
	            if (segment.isVisible) { return segment.end; }
	        }

	        // if no visible segment, return last segment end point
	        return segments[numSegments - 1].end;
	    }
	});


	// Local helper function.
	// Use an array of arguments to call a constructor (function called with `new`).
	// Adapted from https://stackoverflow.com/a/8843181/2263595
	// It is not necessary to use this function if the arguments can be passed separately (i.e. if the number of arguments is limited).
	// - If that is the case, use `new constructor(arg1, arg2)`, for example.
	// It is not necessary to use this function if the function that needs an array of arguments is not supposed to be used as a constructor.
	// - If that is the case, use `f.apply(thisArg, [arg1, arg2...])`, for example.
	function applyToNew(constructor, argsArray) {
	    // The `new` keyword can only be applied to functions that take a limited number of arguments.
	    // - We can fake that with .bind().
	    // - It calls a function (`constructor`, here) with the arguments that were provided to it - effectively transforming an unlimited number of arguments into limited.
	    // - So `new (constructor.bind(thisArg, arg1, arg2...))`
	    // - `thisArg` can be anything (e.g. null) because `new` keyword resets context to the constructor object.
	    // We need to pass in a variable number of arguments to the bind() call.
	    // - We can use .apply().
	    // - So `new (constructor.bind.apply(constructor, [thisArg, arg1, arg2...]))`
	    // - `thisArg` can still be anything because `new` overwrites it.
	    // Finally, to make sure that constructor.bind overwriting is not a problem, we switch to `Function.prototype.bind`.
	    // - So, the final version is `new (Function.prototype.bind.apply(constructor, [thisArg, arg1, arg2...]))`

	    // The function expects `argsArray[0]` to be `thisArg`.
	    // - This means that whatever is sent as the first element will be ignored.
	    // - The constructor will only see arguments starting from argsArray[1].
	    // - So, a new dummy element is inserted at the start of the array.
	    argsArray.unshift(null);

	    return new (Function.prototype.bind.apply(constructor, argsArray));
	}

	// Path segment interface:
	var segmentPrototype = {

	    // virtual
	    bbox: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    clone: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    closestPoint: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    closestPointLength: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    closestPointNormalizedLength: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // Redirect calls to closestPointNormalizedLength() function if closestPointT() is not defined for segment.
	    closestPointT: function(p) {

	        if (this.closestPointNormalizedLength) { return this.closestPointNormalizedLength(p); }

	        throw new Error('Neither closestPointT() nor closestPointNormalizedLength() function is implemented.');
	    },

	    // virtual
	    closestPointTangent: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    divideAt: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    divideAtLength: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // Redirect calls to divideAt() function if divideAtT() is not defined for segment.
	    divideAtT: function(t) {

	        if (this.divideAt) { return this.divideAt(t); }

	        throw new Error('Neither divideAtT() nor divideAt() function is implemented.');
	    },

	    // virtual
	    equals: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    getSubdivisions: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    isDifferentiable: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    isSegment: true,

	    isSubpathStart: false, // true for Moveto segments

	    isVisible: true, // false for Moveto segments

	    // virtual
	    length: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // Return a fraction of result of length() function if lengthAtT() is not defined for segment.
	    lengthAtT: function(t) {

	        if (t <= 0) { return 0; }

	        var length = this.length();

	        if (t >= 1) { return length; }

	        return length * t;
	    },

	    nextSegment: null, // needed for subpath start segment updating

	    // virtual
	    pointAt: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    pointAtLength: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // Redirect calls to pointAt() function if pointAtT() is not defined for segment.
	    pointAtT: function(t) {

	        if (this.pointAt) { return this.pointAt(t); }

	        throw new Error('Neither pointAtT() nor pointAt() function is implemented.');
	    },

	    previousSegment: null, // needed to get segment start property

	    // virtual
	    round: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    subpathStartSegment: null, // needed to get Closepath segment end property

	    // virtual
	    scale: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    serialize: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    tangentAt: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    tangentAtLength: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // Redirect calls to tangentAt() function if tangentAtT() is not defined for segment.
	    tangentAtT: function(t) {

	        if (this.tangentAt) { return this.tangentAt(t); }

	        throw new Error('Neither tangentAtT() nor tangentAt() function is implemented.');
	    },

	    // virtual
	    toString: function() {

	        throw new Error('Declaration missing for virtual function.');
	    },

	    // virtual
	    translate: function() {

	        throw new Error('Declaration missing for virtual function.');
	    }
	};

	// usually directly assigned
	// getter for Closepath
	Object.defineProperty(segmentPrototype, 'end', {

	    configurable: true,

	    enumerable: true,

	    writable: true
	});

	// always a getter
	// always throws error for Moveto
	Object.defineProperty(segmentPrototype, 'start', {
	    // get a reference to the end point of previous segment

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        if (!this.previousSegment) { throw new Error('Missing previous segment. (This segment cannot be the first segment of a path; OR segment has not yet been added to a path.)'); }

	        return this.previousSegment.end;
	    }
	});

	// virtual
	Object.defineProperty(segmentPrototype, 'type', {

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        throw new Error('Bad segment declaration. No type specified.');
	    }
	});

	// Path segment implementations:
	var Lineto = function() {
	    var arguments$1 = arguments;


	    var args = [];
	    var n = arguments.length;
	    for (var i = 0; i < n; i++) {
	        args.push(arguments$1[i]);
	    }

	    if (!(this instanceof Lineto)) { // switching context of `this` to Lineto when called without `new`
	        return applyToNew(Lineto, args);
	    }

	    if (n === 0) {
	        throw new Error('Lineto constructor expects a line, 1 point, or 2 coordinates (none provided).');
	    }

	    var outputArray;

	    if (args[0] instanceof Line) { // lines provided
	        if (n === 1) {
	            this.end = args[0].end.clone();
	            return this;

	        } else {
	            throw new Error('Lineto constructor expects a line, 1 point, or 2 coordinates (' + n + ' lines provided).');
	        }

	    } else if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided
	        if (n === 2) {
	            this.end = new Point(+args[0], +args[1]);
	            return this;

	        } else if (n < 2) {
	            throw new Error('Lineto constructor expects a line, 1 point, or 2 coordinates (' + n + ' coordinates provided).');

	        } else { // this is a poly-line segment
	            var segmentCoords;
	            outputArray = [];
	            for (i = 0; i < n; i += 2) { // coords come in groups of two

	                segmentCoords = args.slice(i, i + 2); // will send one coord if args.length not divisible by 2
	                outputArray.push(applyToNew(Lineto, segmentCoords));
	            }
	            return outputArray;
	        }

	    } else { // points provided (needs to be last to also cover plain objects with x and y)
	        if (n === 1) {
	            this.end = new Point(args[0]);
	            return this;

	        } else { // this is a poly-line segment
	            var segmentPoint;
	            outputArray = [];
	            for (i = 0; i < n; i += 1) {

	                segmentPoint = args[i];
	                outputArray.push(new Lineto(segmentPoint));
	            }
	            return outputArray;
	        }
	    }
	};

	var linetoPrototype = {

	    clone: function() {

	        return new Lineto(this.end);
	    },

	    divideAt: function(ratio) {

	        var line = new Line(this.start, this.end);
	        var divided = line.divideAt(ratio);
	        return [
	            new Lineto(divided[0]),
	            new Lineto(divided[1])
	        ];
	    },

	    divideAtLength: function(length) {

	        var line = new Line(this.start, this.end);
	        var divided = line.divideAtLength(length);
	        return [
	            new Lineto(divided[0]),
	            new Lineto(divided[1])
	        ];
	    },

	    getSubdivisions: function() {

	        return [];
	    },

	    isDifferentiable: function() {

	        if (!this.previousSegment) { return false; }

	        return !this.start.equals(this.end);
	    },

	    round: function(precision) {

	        this.end.round(precision);
	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        this.end.scale(sx, sy, origin);
	        return this;
	    },

	    serialize: function() {

	        var end = this.end;
	        return this.type + ' ' + end.x + ' ' + end.y;
	    },

	    toString: function() {

	        return this.type + ' ' + this.start + ' ' + this.end;
	    },

	    translate: function(tx, ty) {

	        this.end.translate(tx, ty);
	        return this;
	    }
	};

	Object.defineProperty(linetoPrototype, 'type', {

	    configurable: true,

	    enumerable: true,

	    value: 'L'
	});

	Lineto.prototype = extend(segmentPrototype, Line.prototype, linetoPrototype);

	var Curveto = function() {
	    var arguments$1 = arguments;


	    var args = [];
	    var n = arguments.length;
	    for (var i = 0; i < n; i++) {
	        args.push(arguments$1[i]);
	    }

	    if (!(this instanceof Curveto)) { // switching context of `this` to Curveto when called without `new`
	        return applyToNew(Curveto, args);
	    }

	    if (n === 0) {
	        throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (none provided).');
	    }

	    var outputArray;

	    if (args[0] instanceof Curve) { // curves provided
	        if (n === 1) {
	            this.controlPoint1 = args[0].controlPoint1.clone();
	            this.controlPoint2 = args[0].controlPoint2.clone();
	            this.end = args[0].end.clone();
	            return this;

	        } else {
	            throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (' + n + ' curves provided).');
	        }

	    } else if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided
	        if (n === 6) {
	            this.controlPoint1 = new Point(+args[0], +args[1]);
	            this.controlPoint2 = new Point(+args[2], +args[3]);
	            this.end = new Point(+args[4], +args[5]);
	            return this;

	        } else if (n < 6) {
	            throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (' + n + ' coordinates provided).');

	        } else { // this is a poly-bezier segment
	            var segmentCoords;
	            outputArray = [];
	            for (i = 0; i < n; i += 6) { // coords come in groups of six

	                segmentCoords = args.slice(i, i + 6); // will send fewer than six coords if args.length not divisible by 6
	                outputArray.push(applyToNew(Curveto, segmentCoords));
	            }
	            return outputArray;
	        }

	    } else { // points provided (needs to be last to also cover plain objects with x and y)
	        if (n === 3) {
	            this.controlPoint1 = new Point(args[0]);
	            this.controlPoint2 = new Point(args[1]);
	            this.end = new Point(args[2]);
	            return this;

	        } else if (n < 3) {
	            throw new Error('Curveto constructor expects a curve, 3 points, or 6 coordinates (' + n + ' points provided).');

	        } else { // this is a poly-bezier segment
	            var segmentPoints;
	            outputArray = [];
	            for (i = 0; i < n; i += 3) { // points come in groups of three

	                segmentPoints = args.slice(i, i + 3); // will send fewer than three points if args.length is not divisible by 3
	                outputArray.push(applyToNew(Curveto, segmentPoints));
	            }
	            return outputArray;
	        }
	    }
	};

	var curvetoPrototype = {

	    clone: function() {

	        return new Curveto(this.controlPoint1, this.controlPoint2, this.end);
	    },

	    divideAt: function(ratio, opt) {

	        var curve = new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
	        var divided = curve.divideAt(ratio, opt);
	        return [
	            new Curveto(divided[0]),
	            new Curveto(divided[1])
	        ];
	    },

	    divideAtLength: function(length, opt) {

	        var curve = new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
	        var divided = curve.divideAtLength(length, opt);
	        return [
	            new Curveto(divided[0]),
	            new Curveto(divided[1])
	        ];
	    },

	    divideAtT: function(t) {

	        var curve = new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
	        var divided = curve.divideAtT(t);
	        return [
	            new Curveto(divided[0]),
	            new Curveto(divided[1])
	        ];
	    },

	    isDifferentiable: function() {

	        if (!this.previousSegment) { return false; }

	        var start = this.start;
	        var control1 = this.controlPoint1;
	        var control2 = this.controlPoint2;
	        var end = this.end;

	        return !(start.equals(control1) && control1.equals(control2) && control2.equals(end));
	    },

	    round: function(precision) {

	        this.controlPoint1.round(precision);
	        this.controlPoint2.round(precision);
	        this.end.round(precision);
	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        this.controlPoint1.scale(sx, sy, origin);
	        this.controlPoint2.scale(sx, sy, origin);
	        this.end.scale(sx, sy, origin);
	        return this;
	    },

	    serialize: function() {

	        var c1 = this.controlPoint1;
	        var c2 = this.controlPoint2;
	        var end = this.end;
	        return this.type + ' ' + c1.x + ' ' + c1.y + ' ' + c2.x + ' ' + c2.y + ' ' + end.x + ' ' + end.y;
	    },

	    toString: function() {

	        return this.type + ' ' + this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end;
	    },

	    translate: function(tx, ty) {

	        this.controlPoint1.translate(tx, ty);
	        this.controlPoint2.translate(tx, ty);
	        this.end.translate(tx, ty);
	        return this;
	    }
	};

	Object.defineProperty(curvetoPrototype, 'type', {

	    configurable: true,

	    enumerable: true,

	    value: 'C'
	});

	Curveto.prototype = extend(segmentPrototype, Curve.prototype, curvetoPrototype);

	var Moveto = function() {
	    var arguments$1 = arguments;


	    var args = [];
	    var n = arguments.length;
	    for (var i = 0; i < n; i++) {
	        args.push(arguments$1[i]);
	    }

	    if (!(this instanceof Moveto)) { // switching context of `this` to Moveto when called without `new`
	        return applyToNew(Moveto, args);
	    }

	    if (n === 0) {
	        throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (none provided).');
	    }

	    var outputArray;

	    if (args[0] instanceof Line) { // lines provided
	        if (n === 1) {
	            this.end = args[0].end.clone();
	            return this;

	        } else {
	            throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (' + n + ' lines provided).');
	        }

	    } else if (args[0] instanceof Curve) { // curves provided
	        if (n === 1) {
	            this.end = args[0].end.clone();
	            return this;

	        } else {
	            throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (' + n + ' curves provided).');
	        }

	    } else if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided
	        if (n === 2) {
	            this.end = new Point(+args[0], +args[1]);
	            return this;

	        } else if (n < 2) {
	            throw new Error('Moveto constructor expects a line, a curve, 1 point, or 2 coordinates (' + n + ' coordinates provided).');

	        } else { // this is a moveto-with-subsequent-poly-line segment
	            var segmentCoords;
	            outputArray = [];
	            for (i = 0; i < n; i += 2) { // coords come in groups of two

	                segmentCoords = args.slice(i, i + 2); // will send one coord if args.length not divisible by 2
	                if (i === 0) { outputArray.push(applyToNew(Moveto, segmentCoords)); }
	                else { outputArray.push(applyToNew(Lineto, segmentCoords)); }
	            }
	            return outputArray;
	        }

	    } else { // points provided (needs to be last to also cover plain objects with x and y)
	        if (n === 1) {
	            this.end = new Point(args[0]);
	            return this;

	        } else { // this is a moveto-with-subsequent-poly-line segment
	            var segmentPoint;
	            outputArray = [];
	            for (i = 0; i < n; i += 1) { // points come one by one

	                segmentPoint = args[i];
	                if (i === 0) { outputArray.push(new Moveto(segmentPoint)); }
	                else { outputArray.push(new Lineto(segmentPoint)); }
	            }
	            return outputArray;
	        }
	    }
	};

	var movetoPrototype = {

	    bbox: function() {

	        return null;
	    },

	    clone: function() {

	        return new Moveto(this.end);
	    },

	    closestPoint: function() {

	        return this.end.clone();
	    },

	    closestPointNormalizedLength: function() {

	        return 0;
	    },

	    closestPointLength: function() {

	        return 0;
	    },

	    closestPointT: function() {

	        return 1;
	    },

	    closestPointTangent: function() {

	        return null;
	    },

	    divideAt: function() {

	        return [
	            this.clone(),
	            this.clone()
	        ];
	    },

	    divideAtLength: function() {

	        return [
	            this.clone(),
	            this.clone()
	        ];
	    },

	    equals: function(m) {

	        return this.end.equals(m.end);
	    },

	    getSubdivisions: function() {

	        return [];
	    },

	    isDifferentiable: function() {

	        return false;
	    },

	    isSubpathStart: true,

	    isVisible: false,

	    length: function() {

	        return 0;
	    },

	    lengthAtT: function() {

	        return 0;
	    },

	    pointAt: function() {

	        return this.end.clone();
	    },

	    pointAtLength: function() {

	        return this.end.clone();
	    },

	    pointAtT: function() {

	        return this.end.clone();
	    },

	    round: function(precision) {

	        this.end.round(precision);
	        return this;
	    },

	    scale: function(sx, sy, origin) {

	        this.end.scale(sx, sy, origin);
	        return this;
	    },

	    serialize: function() {

	        var end = this.end;
	        return this.type + ' ' + end.x + ' ' + end.y;
	    },

	    tangentAt: function() {

	        return null;
	    },

	    tangentAtLength: function() {

	        return null;
	    },

	    tangentAtT: function() {

	        return null;
	    },

	    toString: function() {

	        return this.type + ' ' + this.end;
	    },

	    translate: function(tx, ty) {

	        this.end.translate(tx, ty);
	        return this;
	    }
	};

	Object.defineProperty(movetoPrototype, 'start', {

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        throw new Error('Illegal access. Moveto segments should not need a start property.');
	    }
	});

	Object.defineProperty(movetoPrototype, 'type', {

	    configurable: true,

	    enumerable: true,

	    value: 'M'
	});

	Moveto.prototype = extend(segmentPrototype, movetoPrototype); // does not inherit from any other geometry object

	var Closepath = function() {
	    var arguments$1 = arguments;


	    var args = [];
	    var n = arguments.length;
	    for (var i = 0; i < n; i++) {
	        args.push(arguments$1[i]);
	    }

	    if (!(this instanceof Closepath)) { // switching context of `this` to Closepath when called without `new`
	        return applyToNew(Closepath, args);
	    }

	    if (n > 0) {
	        throw new Error('Closepath constructor expects no arguments.');
	    }

	    return this;
	};

	var closepathPrototype = {

	    clone: function() {

	        return new Closepath();
	    },

	    divideAt: function(ratio) {

	        var line = new Line(this.start, this.end);
	        var divided = line.divideAt(ratio);
	        return [
	            // if we didn't actually cut into the segment, first divided part can stay as Z
	            (divided[1].isDifferentiable() ? new Lineto(divided[0]) : this.clone()),
	            new Lineto(divided[1])
	        ];
	    },

	    divideAtLength: function(length) {

	        var line = new Line(this.start, this.end);
	        var divided = line.divideAtLength(length);
	        return [
	            // if we didn't actually cut into the segment, first divided part can stay as Z
	            (divided[1].isDifferentiable() ? new Lineto(divided[0]) : this.clone()),
	            new Lineto(divided[1])
	        ];
	    },

	    getSubdivisions: function() {

	        return [];
	    },

	    isDifferentiable: function() {

	        if (!this.previousSegment || !this.subpathStartSegment) { return false; }

	        return !this.start.equals(this.end);
	    },

	    round: function() {

	        return this;
	    },

	    scale: function() {

	        return this;
	    },

	    serialize: function() {

	        return this.type;
	    },

	    toString: function() {

	        return this.type + ' ' + this.start + ' ' + this.end;
	    },

	    translate: function() {

	        return this;
	    }
	};

	Object.defineProperty(closepathPrototype, 'end', {
	    // get a reference to the end point of subpath start segment

	    configurable: true,

	    enumerable: true,

	    get: function() {

	        if (!this.subpathStartSegment) { throw new Error('Missing subpath start segment. (This segment needs a subpath start segment (e.g. Moveto); OR segment has not yet been added to a path.)'); }

	        return this.subpathStartSegment.end;
	    }
	});

	Object.defineProperty(closepathPrototype, 'type', {

	    configurable: true,

	    enumerable: true,

	    value: 'Z'
	});

	Closepath.prototype = extend(segmentPrototype, Line.prototype, closepathPrototype);

	var segmentTypes = Path.segmentTypes = {
	    L: Lineto,
	    C: Curveto,
	    M: Moveto,
	    Z: Closepath,
	    z: Closepath
	};

	Path.regexSupportedData = new RegExp('^[\\s\\d' + Object.keys(segmentTypes).join('') + ',.]*$');

	Path.isDataSupported = function(data) {

	    if (typeof data !== 'string') { return false; }
	    return this.regexSupportedData.test(data);
	};

	var bezier = {

	    // Cubic Bezier curve path through points.
	    // @deprecated
	    // @param {array} points Array of points through which the smooth line will go.
	    // @return {array} SVG Path commands as an array
	    curveThroughPoints: function(points) {

	        console.warn('deprecated');

	        return new Path(Curve.throughPoints(points)).serialize();
	    },

	    // Get open-ended Bezier Spline Control Points.
	    // @deprecated
	    // @param knots Input Knot Bezier spline points (At least two points!).
	    // @param firstControlPoints Output First Control points. Array of knots.length - 1 length.
	    // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length.
	    getCurveControlPoints: function(knots) {

	        console.warn('deprecated');

	        var firstControlPoints = [];
	        var secondControlPoints = [];
	        var n = knots.length - 1;
	        var i;

	        // Special case: Bezier curve should be a straight line.
	        if (n == 1) {
	            // 3P1 = 2P0 + P3
	            firstControlPoints[0] = new Point(
	                (2 * knots[0].x + knots[1].x) / 3,
	                (2 * knots[0].y + knots[1].y) / 3
	            );

	            // P2 = 2P1 – P0
	            secondControlPoints[0] = new Point(
	                2 * firstControlPoints[0].x - knots[0].x,
	                2 * firstControlPoints[0].y - knots[0].y
	            );

	            return [firstControlPoints, secondControlPoints];
	        }

	        // Calculate first Bezier control points.
	        // Right hand side vector.
	        var rhs = [];

	        // Set right hand side X values.
	        for (i = 1; i < n - 1; i++) {
	            rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
	        }

	        rhs[0] = knots[0].x + 2 * knots[1].x;
	        rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;

	        // Get first control points X-values.
	        var x = this.getFirstControlPoints(rhs);

	        // Set right hand side Y values.
	        for (i = 1; i < n - 1; ++i) {
	            rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
	        }

	        rhs[0] = knots[0].y + 2 * knots[1].y;
	        rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;

	        // Get first control points Y-values.
	        var y = this.getFirstControlPoints(rhs);

	        // Fill output arrays.
	        for (i = 0; i < n; i++) {
	            // First control point.
	            firstControlPoints.push(new Point(x[i], y[i]));

	            // Second control point.
	            if (i < n - 1) {
	                secondControlPoints.push(new Point(
	                    2 * knots [i + 1].x - x[i + 1],
	                    2 * knots[i + 1].y - y[i + 1]
	                ));

	            } else {
	                secondControlPoints.push(new Point(
	                    (knots[n].x + x[n - 1]) / 2,
	                    (knots[n].y + y[n - 1]) / 2)
	                );
	            }
	        }

	        return [firstControlPoints, secondControlPoints];
	    },

	    // Divide a Bezier curve into two at point defined by value 't' <0,1>.
	    // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867
	    // @deprecated
	    // @param control points (start, control start, control end, end)
	    // @return a function that accepts t and returns 2 curves.
	    getCurveDivider: function(p0, p1, p2, p3) {

	        console.warn('deprecated');

	        var curve = new Curve(p0, p1, p2, p3);

	        return function divideCurve(t) {

	            var divided = curve.divide(t);

	            return [{
	                p0: divided[0].start,
	                p1: divided[0].controlPoint1,
	                p2: divided[0].controlPoint2,
	                p3: divided[0].end
	            }, {
	                p0: divided[1].start,
	                p1: divided[1].controlPoint1,
	                p2: divided[1].controlPoint2,
	                p3: divided[1].end
	            }];
	        };
	    },

	    // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points.
	    // @deprecated
	    // @param rhs Right hand side vector.
	    // @return Solution vector.
	    getFirstControlPoints: function(rhs) {

	        console.warn('deprecated');

	        var n = rhs.length;
	        // `x` is a solution vector.
	        var x = [];
	        var tmp = [];
	        var b = 2.0;

	        x[0] = rhs[0] / b;

	        // Decomposition and forward substitution.
	        for (var i = 1; i < n; i++) {
	            tmp[i] = 1 / b;
	            b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
	            x[i] = (rhs[i] - x[i - 1]) / b;
	        }

	        for (i = 1; i < n; i++) {
	            // Backsubstitution.
	            x[n - i - 1] -= tmp[n - i] * x[n - i];
	        }

	        return x;
	    },

	    // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on
	    // a parametric curve x = x(t)/w(t), y = y(t)/w(t), ﬁnd the parameter value t
	    // which corresponds to that point.
	    // @deprecated
	    // @param control points (start, control start, control end, end)
	    // @return a function that accepts a point and returns t.
	    getInversionSolver: function(p0, p1, p2, p3) {

	        console.warn('deprecated');

	        var curve = new Curve(p0, p1, p2, p3);

	        return function solveInversion(p) {

	            return curve.closestPointT(p);
	        };
	    }
	};

	var Polygon = function(points) {

	    if (!(this instanceof Polygon)) {
	        return new Polygon(points);
	    }

	    if (typeof points === 'string') {
	        return new Polygon.parse(points);
	    }

	    this.points = (Array.isArray(points) ? points.map(Point) : []);
	};

	Polygon.parse = function(svgString) {
	    return new Polygon(parsePoints(svgString));
	};

	Polygon.fromRect = function(rect) {
	    return new Polygon([
	        rect.topLeft(),
	        rect.topRight(),
	        rect.bottomRight(),
	        rect.bottomLeft()
	    ]);
	};

	Polygon.prototype = extend(Polyline.prototype, {

	    type: types.Polygon,

	    clone: function() {
	        return new Polygon(clonePoints(this.points));
	    },

	    convexHull: function() {
	        return new Polygon(convexHull(this.points));
	    },

	    lengthPoints: function() {
	        var ref = this;
	        var start = ref.start;
	        var end = ref.end;
	        var points = ref.points;
	        if (points.length <= 1 || start.equals(end)) { return points; }
	        return points.concat( [start.clone()]);
	    }

	});

	function exists(shape1, shape2, shape1opt, shape2opt) {
	    switch (shape1.type) {
	        case types.Line: {
	            switch (shape2.type) {
	                case types.Line: {
	                    return lineWithLine(shape1, shape2);
	                }
	            }
	            break;
	        }
	        case types.Ellipse: {
	            switch (shape2.type) {
	                case types.Line: {
	                    return ellipseWithLine(shape1, shape2);
	                }
	                case types.Ellipse: {
	                    return ellipseWithEllipse(shape1, shape2);
	                }
	            }
	            break;
	        }
	        case types.Rect: {
	            switch (shape2.type) {
	                case types.Line: {
	                    return rectWithLine(shape1, shape2);
	                }
	                case types.Ellipse: {
	                    return rectWithEllipse(shape1, shape2);
	                }
	                case types.Rect: {
	                    return rectWithRect(shape1, shape2);
	                }
	            }
	            break;
	        }
	        case types.Polyline: {
	            switch (shape2.type) {
	                case types.Line: {
	                    return polylineWithLine(shape1, shape2);
	                }
	                case types.Ellipse: {
	                    return polylineWithEllipse(shape1, shape2);
	                }
	                case types.Rect: {
	                    return polylineWithRect(shape1, shape2);
	                }
	                case types.Polyline: {
	                    return polylineWithPolyline(shape1, shape2);
	                }
	            }
	            break;
	        }
	        case types.Polygon: {
	            switch (shape2.type) {
	                case types.Line: {
	                    return polygonWithLine(shape1, shape2);
	                }
	                case types.Ellipse: {
	                    return polygonWithEllipse(shape1, shape2);
	                }
	                case types.Rect: {
	                    return polygonWithRect(shape1, shape2);
	                }
	                case types.Polyline: {
	                    return polygonWithPolyline(shape1, shape2);
	                }
	                case types.Polygon: {
	                    return polygonWithPolygon(shape1, shape2);
	                }
	            }
	            break;
	        }
	        case types.Path: {
	            switch (shape2.type) {
	                case types.Line: {
	                    return pathWithLine(shape1, shape2, shape1opt);
	                }
	                case types.Ellipse: {
	                    return pathWithEllipse(shape1, shape2, shape1opt);
	                }
	                case types.Rect: {
	                    return pathWithRect(shape1, shape2, shape1opt);
	                }
	                case types.Polyline: {
	                    return pathWithPolyline(shape1, shape2, shape1opt);
	                }
	                case types.Polygon: {
	                    return pathWithPolygon(shape1, shape2, shape1opt);
	                }
	                case types.Path: {
	                    return pathWithPath(shape1, shape2, shape1opt, shape2opt);
	                }
	            }
	            break;
	        }
	    }
	    // None of the cases above
	    switch (shape2.type) {
	        case types.Ellipse:
	        case types.Rect:
	        case types.Polyline:
	        case types.Polygon:
	        case types.Path: {
	            return exists(shape2, shape1, shape2opt, shape1opt);
	        }
	        default: {
	            throw Error(("The intersection for " + shape1 + " and " + shape2 + " could not be found."));
	        }
	    }
	}

	/* Line */

	function lineWithLine(line1, line2) {
	    var x1 = line1.start.x;
	    var y1 = line1.start.y;
	    var x2 = line1.end.x;
	    var y2 = line1.end.y;
	    var x3 = line2.start.x;
	    var y3 = line2.start.y;
	    var x4 = line2.end.x;
	    var y4 = line2.end.y;
	    var s1x = x2 - x1;
	    var s1y = y2 - y1;
	    var s2x = x4 - x3;
	    var s2y = y4 - y3;
	    var s3x = x1 - x3;
	    var s3y = y1 - y3;
	    var p = s1x * s2y - s2x * s1y;
	    var s = (s1x * s3y - s1y * s3x) / p;
	    var t = (s2x * s3y - s2y * s3x) / p;
	    return s >= 0 && s <= 1 && t >= 0 && t <= 1;
	}

	/* Ellipse */

	function ellipseWithLine(ellipse, line) {
	    var rex = ellipse.a;
	    var rey = ellipse.b;
	    var xe = ellipse.x;
	    var ye = ellipse.y;
	    var x1 = line.start.x - xe;
	    var x2 = line.end.x - xe;
	    var y1 = line.start.y - ye;
	    var y2 = line.end.y - ye;
	    var rex_2 = rex * rex;
	    var rey_2 = rey * rey;
	    var dx = x2 - x1;
	    var dy = y2 - y1;
	    var A = dx * dx / rex_2 + dy * dy / rey_2;
	    var B = 2 * x1 * dx / rex_2 + 2 * y1 * dy / rey_2;
	    var C = x1 * x1 / rex_2 + y1 * y1 / rey_2 - 1;
	    var D = B * B - 4 * A * C;
	    if (D === 0) {
	        var t = -B / 2 / A;
	        return t >= 0 && t <= 1;
	    } else if (D > 0) {
	        var sqrt = Math.sqrt(D);
	        var t1 = (-B + sqrt) / 2 / A;
	        var t2 = (-B - sqrt) / 2 / A;
	        return (t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1);
	    }
	    return false;
	}

	function ellipseWithEllipse(ellipse1, ellipse2) {
	    return _ellipsesIntersection(ellipse1, 0, ellipse2, 0);
	}

	/* Rect */

	function rectWithLine(rect, line) {
	    var start = line.start;
	    var end = line.end;
	    var x = rect.x;
	    var y = rect.y;
	    var width = rect.width;
	    var height = rect.height;
	    if (
	        (start.x > x + width && end.x > x + width)
	        || (start.x < x && end.x < x)
	        || (start.y > y + height && end.y > y + height)
	        || (start.y < y && end.y < y)
	    ) {
	        return false;
	    }
	    if (rect.containsPoint(line.start) || rect.containsPoint(line.end)) {
	        return true;
	    }
	    return lineWithLine(rect.topLine(), line)
	        || lineWithLine(rect.rightLine(), line)
	        || lineWithLine(rect.bottomLine(), line)
	        || lineWithLine(rect.leftLine(), line);
	}

	function rectWithEllipse(rect, ellipse) {
	    if (!rectWithRect(rect, Rect.fromEllipse(ellipse))) { return false; }
	    return polygonWithEllipse(Polygon.fromRect(rect), ellipse);
	}

	function rectWithRect(rect1, rect2) {
	    return rect1.x < rect2.x + rect2.width
	        && rect1.x + rect1.width > rect2.x
	        && rect1.y < rect2.y + rect2.height
	        && rect1.y + rect1.height > rect2.y;
	}

	/* Polyline */

	function polylineWithLine(polyline, line) {
	    return _polylineWithLine(polyline, line, { interior: false });
	}

	function polylineWithEllipse(polyline, ellipse) {
	    return _polylineWithEllipse(polyline, ellipse, { interior: false });
	}

	function polylineWithRect(polyline, rect) {
	    return _polylineWithRect(polyline, rect, { interior: false });
	}

	function polylineWithPolyline(polyline1, polyline2) {
	    return _polylineWithPolyline(polyline1, polyline2, { interior: false });
	}

	/* Polygon */

	function polygonWithLine(polygon, line) {
	    return _polylineWithLine(polygon, line, { interior: true });
	}

	function polygonWithEllipse(polygon, ellipse) {
	    return _polylineWithEllipse(polygon, ellipse, { interior: true });
	}

	function polygonWithRect(polygon, rect) {
	    return _polylineWithRect(polygon, rect, { interior: true });
	}

	function polygonWithPolyline(polygon, polyline) {
	    return _polylineWithPolyline(polygon, polyline, { interior: true });
	}

	function polygonWithPolygon(polygon1, polygon2) {
	    return _polylineWithPolygon(polygon1, polygon2, { interior: true });
	}

	/* Path */

	function pathWithLine(path, line, pathOpt) {
	    return path.getSubpaths().some(function (subpath) {
	        var ref = subpath.toPolylines(pathOpt);
	        var polyline = ref[0];
	        var ref$1 = subpath.getSegment(-1);
	        var type = ref$1.type;
	        if (type === 'Z') {
	            return polygonWithLine(polyline, line);
	        } else {
	            return polylineWithLine(polyline, line);
	        }
	    });
	}

	function pathWithEllipse(path, ellipse, pathOpt) {
	    return path.getSubpaths().some(function (subpath) {
	        var ref = subpath.toPolylines(pathOpt);
	        var polyline = ref[0];
	        var ref$1 = subpath.getSegment(-1);
	        var type = ref$1.type;
	        if (type === 'Z') {
	            return polygonWithEllipse(polyline, ellipse);
	        } else {
	            return polylineWithEllipse(polyline, ellipse);
	        }
	    });
	}

	function pathWithRect(path, rect, pathOpt) {
	    return pathWithPolygon(path, Polygon.fromRect(rect), pathOpt);
	}

	function pathWithPolyline(path, polyline, pathOpt) {
	    return _pathWithPolyline(path, polyline, pathOpt, { interior: false });
	}

	function pathWithPolygon(path, polygon, pathOpt) {
	    return _pathWithPolyline(path, polygon, pathOpt, { interior: true });
	}

	function pathWithPath(path1, path2, pathOpt1, pathOpt2) {
	    return path1.getSubpaths().some(function (subpath) {
	        var ref = subpath.toPolylines(pathOpt1);
	        var polyline1 = ref[0];
	        var ref$1 = subpath.getSegment(-1);
	        var type = ref$1.type;
	        if (type === 'Z') {
	            return pathWithPolygon(path2, polyline1, pathOpt2);
	        } else {
	            return pathWithPolyline(path2, polyline1, pathOpt2);
	        }
	    });
	}

	function _polylineWithLine(polyline, line, opt) {
	    if ( opt === void 0 ) opt = {};

	    var interior = opt.interior; if ( interior === void 0 ) interior = false;
	    var thisPoints;
	    if (interior) {
	        if (polyline.containsPoint(line.start)) {
	            // If any point of the polyline lies inside this polygon (interior = true)
	            // there is an intersection (we've chosen the start point)
	            return true;
	        }
	        var start = polyline.start;
	        var end = polyline.end;
	        var points = polyline.points;
	        thisPoints = end.equals(start) ? points : points.concat( [start]);
	    } else {
	        thisPoints = polyline.points;
	    }
	    var length = thisPoints.length;
	    var segment = new Line();
	    for (var i = 0; i < length - 1; i++) {
	        segment.start = thisPoints[i];
	        segment.end = thisPoints[i + 1];
	        if (lineWithLine(line, segment)) {
	            return true;
	        }
	    }
	    return false;
	}

	function _polylineWithEllipse(polyline, ellipse, opt) {
	    if ( opt === void 0 ) opt = {};

	    var start = polyline.start;
	    var end = polyline.end;
	    var points = polyline.points;
	    if (ellipse.containsPoint(start)) {
	        return true;
	    }
	    var thisPoints;
	    var interior = opt.interior; if ( interior === void 0 ) interior = false;
	    if (interior) {
	        if (polyline.containsPoint(ellipse.center())) {
	            // If any point of the ellipse lies inside this polygon (interior = true)
	            // there is an intersection (we've chosen the center point)
	            return true;
	        }
	        thisPoints = end.equals(start) ? points : points.concat( [start]);
	    } else {
	        thisPoints = points;
	    }

	    var length = thisPoints.length;
	    var segment = new Line();
	    for (var i = 0; i < length - 1; i++) {
	        segment.start = thisPoints[i];
	        segment.end = thisPoints[i + 1];
	        if (ellipseWithLine(ellipse, segment)) {
	            return true;
	        }
	    }
	    return false;
	}

	function _polylineWithRect(polyline, rect, opt) {
	    var polygon = Polygon.fromRect(rect);
	    return _polylineWithPolygon(polyline, polygon, opt);
	}

	function _pathWithPolyline(path, polyline1, pathOpt, opt) {
	    return path.getSubpaths().some(function (subpath) {
	        var ref = subpath.toPolylines(pathOpt);
	        var polyline2 = ref[0];
	        var ref$1 = subpath.getSegment(-1);
	        var type = ref$1.type;
	        if (type === 'Z') {
	            return _polylineWithPolygon(polyline1, polyline2, opt);
	        } else {
	            return _polylineWithPolyline(polyline1, polyline2, opt);
	        }
	    });
	}

	function _polylineWithPolyline(polyline1, polyline2, opt) {
	    if ( opt === void 0 ) opt = {};

	    var interior = opt.interior; if ( interior === void 0 ) interior = false;
	    var thisPolyline;
	    if (interior) {
	        var start = polyline2.start;
	        if (polyline1.containsPoint(start)) {
	            // If any point of the polyline lies inside this polygon (interior = true)
	            // there is an intersection (we've chosen the start point)
	            return true;
	        }
	        thisPolyline = polyline1.clone().close();
	    } else {
	        thisPolyline = polyline1;
	    }
	    var otherPoints = polyline2.points;
	    var length = otherPoints.length;
	    var segment = new Line();
	    for (var i = 0; i < length - 1; i++) {
	        segment.start = otherPoints[i];
	        segment.end = otherPoints[i + 1];
	        if (polylineWithLine(thisPolyline, segment)) {
	            return true;
	        }
	    }
	    return false;
	}

	function _polylineWithPolygon(polyline, polygon, opt) {
	    return polygon.containsPoint(polyline.start) || _polylineWithPolyline(polyline, polygon.clone().close(), opt);
	}

	function _ellipsesIntersection(e1, w1, e2, w2) {
	    var cos = Math.cos;
	    var sin = Math.sin;
	    var sinW1 = sin(w1);
	    var cosW1 = cos(w1);
	    var sinW2 = sin(w2);
	    var cosW2 = cos(w2);
	    var sinW1s = sinW1 * sinW1;
	    var cosW1s = cosW1 * cosW1;
	    var sinCos1 = sinW1 * cosW1;
	    var sinW2s = sinW2 * sinW2;
	    var cosW2s = cosW2 * cosW2;
	    var sinCos2 = sinW2 * cosW2;
	    var a1s = e1.a * e1.a;
	    var b1s = e1.b * e1.b;
	    var a2s = e2.a * e2.a;
	    var b2s = e2.b * e2.b;
	    var A1 = a1s * sinW1s + b1s * cosW1s;
	    var A2 = a2s * sinW2s + b2s * cosW2s;
	    var B1 = a1s * cosW1s + b1s * sinW1s;
	    var B2 = a2s * cosW2s + b2s * sinW2s;
	    var C1 = 2 * (b1s - a1s) * sinCos1;
	    var C2 = 2 * (b2s - a2s) * sinCos2;
	    var D1 = (-2 * A1 * e1.x - C1 * e1.y);
	    var D2 = (-2 * A2 * e2.x - C2 * e2.y);
	    var E1 = (-C1 * e1.x - 2 * B1 * e1.y);
	    var E2 = (-C2 * e2.x - 2 * B2 * e2.y);
	    var F1 = A1 * e1.x * e1.x + B1 * e1.y * e1.y + C1 * e1.x * e1.y - a1s * b1s;
	    var F2 = A2 * e2.x * e2.x + B2 * e2.y * e2.y + C2 * e2.x * e2.y - a2s * b2s;

	    C1 = C1 / 2;
	    C2 = C2 / 2;
	    D1 = D1 / 2;
	    D2 = D2 / 2;
	    E1 = E1 / 2;
	    E2 = E2 / 2;

	    var l3 = det3([
	        [A1, C1, D1],
	        [C1, B1, E1],
	        [D1, E1, F1]
	    ]);
	    var l0 = det3([
	        [A2, C2, D2],
	        [C2, B2, E2],
	        [D2, E2, F2]
	    ]);
	    var l2 = 0.33333333 * (det3([
	        [A2, C1, D1],
	        [C2, B1, E1],
	        [D2, E1, F1]
	    ]) + det3([
	        [A1, C2, D1],
	        [C1, B2, E1],
	        [D1, E2, F1]
	    ]) + det3([
	        [A1, C1, D2],
	        [C1, B1, E2],
	        [D1, E1, F2]
	    ]));
	    var l1 = 0.33333333 * (det3([
	        [A1, C2, D2],
	        [C1, B2, E2],
	        [D1, E2, F2]
	    ]) + det3([
	        [A2, C1, D2],
	        [C2, B1, E2],
	        [D2, E1, F2]
	    ]) + det3([
	        [A2, C2, D1],
	        [C2, B2, E1],
	        [D2, E2, F1]
	    ]));

	    var delta1 = det2([
	        [l3, l2],
	        [l2, l1]
	    ]);
	    var delta2 = det2([
	        [l3, l1],
	        [l2, l0]
	    ]);
	    var delta3 = det2([
	        [l2, l1],
	        [l1, l0]
	    ]);

	    var dP = det2([
	        [2 * delta1, delta2],
	        [delta2, 2 * delta3]
	    ]);

	    if (dP > 0 && (l1 > 0 || l2 > 0)) {
	        return false;
	    }
	    return true;
	}

	function det2(m) {
	    return m[0][0] * m[1][1] - m[0][1] * m[1][0];
	}

	function det3(m) {
	    return m[0][0] * m[1][1] * m[2][2] -
	        m[0][0] * m[1][2] * m[2][1] -
	        m[0][1] * m[1][0] * m[2][2] +
	        m[0][1] * m[1][2] * m[2][0] +
	        m[0][2] * m[1][0] * m[2][1] -
	        m[0][2] * m[1][1] * m[2][0];
	}

	var _intersection = ({
		exists: exists,
		lineWithLine: lineWithLine,
		ellipseWithLine: ellipseWithLine,
		ellipseWithEllipse: ellipseWithEllipse,
		rectWithLine: rectWithLine,
		rectWithEllipse: rectWithEllipse,
		rectWithRect: rectWithRect,
		polylineWithLine: polylineWithLine,
		polylineWithEllipse: polylineWithEllipse,
		polylineWithRect: polylineWithRect,
		polylineWithPolyline: polylineWithPolyline,
		polygonWithLine: polygonWithLine,
		polygonWithEllipse: polygonWithEllipse,
		polygonWithRect: polygonWithRect,
		polygonWithPolyline: polygonWithPolyline,
		polygonWithPolygon: polygonWithPolygon,
		pathWithLine: pathWithLine,
		pathWithEllipse: pathWithEllipse,
		pathWithRect: pathWithRect,
		pathWithPolyline: pathWithPolyline,
		pathWithPolygon: pathWithPolygon,
		pathWithPath: pathWithPath
	});

	// Geometry library.
	var intersection = _intersection;

	var g = ({
		intersection: intersection,
		scale: scale,
		normalizeAngle: normalizeAngle,
		snapToGrid: snapToGrid,
		toDeg: toDeg,
		toRad: toRad,
		random: random,
		bezier: bezier,
		Curve: Curve,
		Ellipse: Ellipse,
		ellipse: ellipse,
		Line: Line,
		line: line,
		Path: Path,
		Point: Point,
		point: point,
		Polyline: Polyline,
		Polygon: Polygon,
		Rect: Rect,
		rect: rect,
		types: types
	});

	// Vectorizer.

	var V = (function() {

	    var hasSvg = typeof window === 'object' && !!window.SVGAngle;

	    // SVG support is required.
	    if (!hasSvg) {

	        // Return a function that throws an error when it is used.
	        return function() {
	            throw new Error('SVG is required to use Vectorizer.');
	        };
	    }

	    // XML namespaces.
	    var ns = {
	        svg: 'http://www.w3.org/2000/svg',
	        xmlns: 'http://www.w3.org/2000/xmlns/',
	        xml: 'http://www.w3.org/XML/1998/namespace',
	        xlink: 'http://www.w3.org/1999/xlink',
	        xhtml: 'http://www.w3.org/1999/xhtml'
	    };

	    var SVGVersion = '1.1';

	    // Declare shorthands to the most used math functions.
	    var math = Math;
	    var PI = math.PI;
	    var atan2 = math.atan2;
	    var sqrt = math.sqrt;
	    var min = math.min;
	    var max = math.max;
	    var cos = math.cos;
	    var sin = math.sin;

	    var V = function(el, attrs, children) {

	        // This allows using V() without the new keyword.
	        if (!(this instanceof V)) {
	            return V.apply(Object.create(V.prototype), arguments);
	        }

	        if (!el) { return; }

	        if (V.isV(el)) {
	            el = el.node;
	        }

	        attrs = attrs || {};

	        if (V.isString(el)) {

	            el = el.trim();

	            if (el.toLowerCase() === 'svg') {

	                // Create a new SVG canvas.
	                el = V.createSvgDocument();

	            } else if (el[0] === '<') {

	                // Create element from an SVG string.
	                // Allows constructs of type: `document.appendChild(V('<rect></rect>').node)`.

	                var svgDoc = V.createSvgDocument(el);

	                // Note that `V()` might also return an array should the SVG string passed as
	                // the first argument contain more than one root element.
	                if (svgDoc.childNodes.length > 1) {

	                    // Map child nodes to `V`s.
	                    var arrayOfVels = [];
	                    var i, len;

	                    for (i = 0, len = svgDoc.childNodes.length; i < len; i++) {

	                        var childNode = svgDoc.childNodes[i];
	                        arrayOfVels.push(new V(document.importNode(childNode, true)));
	                    }

	                    return arrayOfVels;
	                }

	                el = document.importNode(svgDoc.firstChild, true);

	            } else {

	                el = document.createElementNS(ns.svg, el);
	            }

	            V.ensureId(el);
	        }

	        this.node = el;

	        this.setAttributes(attrs);

	        if (children) {
	            this.append(children);
	        }

	        return this;
	    };

	    var VPrototype = V.prototype;

	    Object.defineProperty(VPrototype, 'id', {
	        enumerable: true,
	        get: function() {
	            return this.node.id;
	        },
	        set: function(id) {
	            this.node.id = id;
	        }
	    });

	    /**
	     * @param {SVGGElement} toElem
	     * @returns {SVGMatrix}
	     */
	    VPrototype.getTransformToElement = function(target) {
	        var node = this.node;
	        if (V.isSVGGraphicsElement(target) && V.isSVGGraphicsElement(node)) {
	            var targetCTM = V.toNode(target).getScreenCTM();
	            var nodeCTM = node.getScreenCTM();
	            if (targetCTM && nodeCTM) {
	                return targetCTM.inverse().multiply(nodeCTM);
	            }
	        }
	        // Could not get actual transformation matrix
	        return V.createSVGMatrix();
	    };

	    /**
	     * @param {SVGMatrix} matrix
	     * @param {Object=} opt
	     * @returns {Vectorizer|SVGMatrix} Setter / Getter
	     */
	    VPrototype.transform = function(matrix, opt) {

	        var node = this.node;
	        if (V.isUndefined(matrix)) {
	            return V.transformStringToMatrix(this.attr('transform'));
	        }

	        if (opt && opt.absolute) {
	            return this.attr('transform', V.matrixToTransformString(matrix));
	        }

	        var svgTransform = V.createSVGTransform(matrix);
	        node.transform.baseVal.appendItem(svgTransform);
	        return this;
	    };

	    VPrototype.translate = function(tx, ty, opt) {

	        opt = opt || {};
	        ty = ty || 0;

	        var transformAttr = this.attr('transform') || '';
	        var transform = V.parseTransformString(transformAttr);
	        transformAttr = transform.value;
	        // Is it a getter?
	        if (V.isUndefined(tx)) {
	            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;
	    };

	    VPrototype.rotate = function(angle, cx, cy, opt) {

	        opt = opt || {};

	        var transformAttr = this.attr('transform') || '';
	        var transform = V.parseTransformString(transformAttr);
	        transformAttr = transform.value;

	        // Is it a getter?
	        if (V.isUndefined(angle)) {
	            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.
	    VPrototype.scale = function(sx, sy) {

	        sy = V.isUndefined(sy) ? sx : sy;

	        var transformAttr = this.attr('transform') || '';
	        var transform = V.parseTransformString(transformAttr);
	        transformAttr = transform.value;

	        // Is it a getter?
	        if (V.isUndefined(sx)) {
	            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.
	    VPrototype.bbox = function(withoutTransformations, target) {

	        var box;
	        var node = this.node;
	        var ownerSVGElement = node.ownerSVGElement;

	        // 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 (!ownerSVGElement) {
	            return new Rect(0, 0, 0, 0);
	        }

	        try {

	            box = node.getBBox();

	        } catch (e) {

	            // Fallback for IE.
	            box = {
	                x: node.clientLeft,
	                y: node.clientTop,
	                width: node.clientWidth,
	                height: node.clientHeight
	            };
	        }

	        if (withoutTransformations) {
	            return new Rect(box);
	        }

	        var matrix = this.getTransformToElement(target || ownerSVGElement);

	        return V.transformRect(box, matrix);
	    };

	    // Returns an SVGRect that contains coordinates and dimensions of the real bounding box,
	    // i.e. after transformations are applied.
	    // Fixes a browser implementation bug that returns incorrect bounding boxes for groups of svg elements.
	    // Takes an (Object) `opt` argument (optional) with the following attributes:
	    // (Object) `target` (optional): if not undefined, transform bounding boxes relative to `target`; if undefined, transform relative to this
	    // (Boolean) `recursive` (optional): if true, recursively enter all groups and get a union of element bounding boxes (svg bbox fix); if false or undefined, return result of native function this.node.getBBox();
	    VPrototype.getBBox = function(opt) {

	        var options = {};

	        var outputBBox;
	        var node = this.node;
	        var ownerSVGElement = node.ownerSVGElement;

	        // 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 the element is not an SVGGraphicsElement, we could not measure the bounding box either
	        if (!ownerSVGElement || !V.isSVGGraphicsElement(node)) {
	            return new Rect(0, 0, 0, 0);
	        }

	        if (opt) {
	            if (opt.target) { // check if target exists
	                options.target = V.toNode(opt.target); // works for V objects, jquery objects, and node objects
	            }
	            if (opt.recursive) {
	                options.recursive = opt.recursive;
	            }
	        }

	        if (!options.recursive) {
	            try {
	                outputBBox = node.getBBox();
	            } catch (e) {
	                // Fallback for IE.
	                outputBBox = {
	                    x: node.clientLeft,
	                    y: node.clientTop,
	                    width: node.clientWidth,
	                    height: node.clientHeight
	                };
	            }

	            if (!options.target) {
	                // transform like this (that is, not at all)
	                return new Rect(outputBBox);
	            } else {
	                // transform like target
	                var matrix = this.getTransformToElement(options.target);
	                return V.transformRect(outputBBox, matrix);
	            }
	        } else { // if we want to calculate the bbox recursively
	            // browsers report correct bbox around svg elements (one that envelops the path lines tightly)
	            // but some browsers fail to report the same bbox when the elements are in a group (returning a looser bbox that also includes control points, like node.getClientRect())
	            // this happens even if we wrap a single svg element into a group!
	            // this option setting makes the function recursively enter all the groups from this and deeper, get bboxes of the elements inside, then return a union of those bboxes

	            var children = this.children();
	            var n = children.length;

	            if (n === 0) {
	                return this.getBBox({ target: options.target, recursive: false });
	            }

	            // recursion's initial pass-through setting:
	            // recursive passes-through just keep the target as whatever was set up here during the initial pass-through
	            if (!options.target) {
	                // transform children/descendants like this (their parent/ancestor)
	                options.target = this;
	            } // else transform children/descendants like target

	            for (var i = 0; i < n; i++) {
	                var currentChild = children[i];

	                var childBBox;

	                // if currentChild is not a group element, get its bbox with a nonrecursive call
	                if (currentChild.children().length === 0) {
	                    childBBox = currentChild.getBBox({ target: options.target, recursive: false });
	                } else {
	                    // if currentChild is a group element (determined by checking the number of children), enter it with a recursive call
	                    childBBox = currentChild.getBBox({ target: options.target, recursive: true });
	                }

	                if (!outputBBox) {
	                    // if this is the first iteration
	                    outputBBox = childBBox;
	                } else {
	                    // make a new bounding box rectangle that contains this child's bounding box and previous bounding box
	                    outputBBox = outputBBox.union(childBBox);
	                }
	            }

	            return outputBBox;
	        }
	    };

	    // Text() helpers

	    function createTextPathNode(attrs, vel) {
	        attrs || (attrs = {});
	        var textPathElement = V('textPath');
	        var d = attrs.d;
	        if (d && attrs['xlink:href'] === undefined) {
	            // If `opt.attrs` 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.
	            // Wrap the text in the SVG <textPath> element that points
	            // to a path defined by `opt.attrs` inside the `<defs>` element.
	            var linkedPath = V('path').attr('d', d).appendTo(vel.defs());
	            textPathElement.attr('xlink:href', '#' + linkedPath.id);
	        }
	        if (V.isObject(attrs)) {
	            // 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.
	            textPathElement.attr(attrs);
	        }
	        return textPathElement.node;
	    }

	    function annotateTextLine(lineNode, lineAnnotations, opt) {
	        opt || (opt = {});
	        var includeAnnotationIndices = opt.includeAnnotationIndices;
	        var eol = opt.eol;
	        var lineHeight = opt.lineHeight;
	        var baseSize = opt.baseSize;
	        var maxFontSize = 0;
	        var fontMetrics = {};
	        var lastJ = lineAnnotations.length - 1;
	        for (var j = 0; j <= lastJ; j++) {
	            var annotation = lineAnnotations[j];
	            var fontSize = null;
	            if (V.isObject(annotation)) {
	                var annotationAttrs = annotation.attrs;
	                var vTSpan = V('tspan', annotationAttrs);
	                var tspanNode = vTSpan.node;
	                var t = annotation.t;
	                if (eol && j === lastJ) { t += eol; }
	                tspanNode.textContent = t;
	                // Per annotation className
	                var annotationClass = annotationAttrs['class'];
	                if (annotationClass) { vTSpan.addClass(annotationClass); }
	                // 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.
	                if (includeAnnotationIndices) { vTSpan.attr('annotations', annotation.annotations); }
	                // Check for max font size
	                fontSize = parseFloat(annotationAttrs['font-size']);
	                if (!isFinite(fontSize)) { fontSize = baseSize; }
	                if (fontSize && fontSize > maxFontSize) { maxFontSize = fontSize; }
	            } else {
	                if (eol && j === lastJ) { annotation += eol; }
	                tspanNode = document.createTextNode(annotation || ' ');
	                if (baseSize && baseSize > maxFontSize) { maxFontSize = baseSize; }
	            }
	            lineNode.appendChild(tspanNode);
	        }

	        if (maxFontSize) { fontMetrics.maxFontSize = maxFontSize; }
	        if (lineHeight) {
	            fontMetrics.lineHeight = lineHeight;
	        } else if (maxFontSize) {
	            fontMetrics.lineHeight = (maxFontSize * 1.2);
	        }
	        return fontMetrics;
	    }

	    var emRegex = /em$/;

	    function convertEmToPx(em, fontSize) {
	        var numerical = parseFloat(em);
	        if (emRegex.test(em)) { return numerical * fontSize; }
	        return numerical;
	    }

	    function calculateDY(alignment, linesMetrics, baseSizePx, lineHeight) {
	        if (!Array.isArray(linesMetrics)) { return 0; }
	        var n = linesMetrics.length;
	        if (!n) { return 0; }
	        var lineMetrics = linesMetrics[0];
	        var flMaxFont = convertEmToPx(lineMetrics.maxFontSize, baseSizePx) || baseSizePx;
	        var rLineHeights = 0;
	        var lineHeightPx = convertEmToPx(lineHeight, baseSizePx);
	        for (var i = 1; i < n; i++) {
	            lineMetrics = linesMetrics[i];
	            var iLineHeight = convertEmToPx(lineMetrics.lineHeight, baseSizePx) || lineHeightPx;
	            rLineHeights += iLineHeight;
	        }
	        var llMaxFont = convertEmToPx(lineMetrics.maxFontSize, baseSizePx) || baseSizePx;
	        var dy;
	        switch (alignment) {
	            case 'middle':
	                dy = (flMaxFont / 2) - (0.15 * llMaxFont) - (rLineHeights / 2);
	                break;
	            case 'bottom':
	                dy = -(0.25 * llMaxFont) - rLineHeights;
	                break;
	            default:
	            case 'top':
	                dy = (0.8 * flMaxFont);
	                break;
	        }
	        return dy;
	    }

	    VPrototype.text = function(content, opt) {

	        if (content && typeof content !== 'string') { throw new Error('Vectorizer: text() expects the first argument to be a string.'); }

	        // 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 = V.sanitizeText(content);
	        opt || (opt = {});
	        // Should we allow the text to be selected?
	        var displayEmpty = opt.displayEmpty;
	        // End of Line character
	        var eol = opt.eol;
	        // Text along path
	        var textPath = opt.textPath;
	        // Vertical shift
	        var verticalAnchor = opt.textVerticalAnchor;
	        var namedVerticalAnchor = (verticalAnchor === 'middle' || verticalAnchor === 'bottom' || verticalAnchor === 'top');
	        // Horizontal shift applied to all the lines but the first.
	        var x = opt.x;
	        if (x === undefined) { x = this.attr('x') || 0; }
	        // Annotations
	        var iai = opt.includeAnnotationIndices;
	        var annotations = opt.annotations;
	        if (annotations && !V.isArray(annotations)) { annotations = [annotations]; }
	        // Shift all the <tspan> but first by one line (`1em`)
	        var defaultLineHeight = opt.lineHeight;
	        var autoLineHeight = (defaultLineHeight === 'auto');
	        var lineHeight = (autoLineHeight) ? '1.5em' : (defaultLineHeight || '1em');
	        // Clearing the element
	        this.empty();
	        this.attr({
	            // Preserve spaces. In other words, we do not want consecutive spaces to get collapsed to one.
	            'xml:space': 'preserve',
	            // 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.
	            'display': (content || displayEmpty) ? null : 'none'
	        });

	        // Set default font-size if none
	        var fontSize = parseFloat(this.attr('font-size'));
	        if (!fontSize) {
	            fontSize = 16;
	            if (namedVerticalAnchor || annotations) { this.attr('font-size', fontSize); }
	        }

	        var doc = document;
	        var containerNode;
	        if (textPath) {
	            // Now all the `<tspan>`s will be inside the `<textPath>`.
	            if (typeof textPath === 'string') { textPath = { d: textPath }; }
	            containerNode = createTextPathNode(textPath, this);
	        } else {
	            containerNode = doc.createDocumentFragment();
	        }
	        var offset = 0;
	        var lines = content.split('\n');
	        var linesMetrics = [];
	        var annotatedY;
	        for (var i = 0, lastI = lines.length - 1; i <= lastI; i++) {
	            var dy = lineHeight;
	            var lineClassName = 'v-line';
	            var lineNode = doc.createElementNS(ns.svg, 'tspan');
	            var line = lines[i];
	            var lineMetrics;
	            if (line) {
	                if (annotations) {
	                    // Find the *compacted* annotations for this line.
	                    var lineAnnotations = V.annotateString(line, annotations, {
	                        offset: -offset,
	                        includeAnnotationIndices: iai
	                    });
	                    lineMetrics = annotateTextLine(lineNode, lineAnnotations, {
	                        includeAnnotationIndices: iai,
	                        eol: (i !== lastI && eol),
	                        lineHeight: (autoLineHeight) ? null : lineHeight,
	                        baseSize: fontSize
	                    });
	                    // Get the line height based on the biggest font size in the annotations for this line.
	                    var iLineHeight = lineMetrics.lineHeight;
	                    if (iLineHeight && autoLineHeight && i !== 0) { dy = iLineHeight; }
	                    if (i === 0) { annotatedY = lineMetrics.maxFontSize * 0.8; }
	                } else {
	                    if (eol && i !== lastI) { line += eol; }
	                    lineNode.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.
	                lineNode.textContent = '-';
	                lineClassName += ' v-empty-line';
	                // 'opacity' needs to be specified with fill, stroke. Opacity without specification
	                // is not applied in Firefox
	                var lineNodeStyle = lineNode.style;
	                lineNodeStyle.fillOpacity = 0;
	                lineNodeStyle.strokeOpacity = 0;
	                if (annotations) {
	                    // Empty line with annotations.
	                    lineMetrics = {};
	                    lineAnnotations = V.findAnnotationsAtIndex(annotations, offset);
	                    var lineFontSize = fontSize;
	                    // Check if any of the annotations overrides the font size.
	                    for (var j = lineAnnotations.length; j > 0; j--) {
	                        var attrs = lineAnnotations[j - 1].attrs;
	                        if (!attrs || !('font-size' in attrs)) { continue; }
	                        var fs = parseFloat(attrs['font-size']);
	                        if (isFinite(fs)) {
	                            lineFontSize = fs;
	                            break;
	                        }
	                    }
	                    if (autoLineHeight) {
	                        if (i > 0) {
	                            dy = lineFontSize * 1.2;
	                        } else {
	                            annotatedY = lineFontSize * 0.8;
	                        }
	                    }
	                    // The font size is important for the native selection box height.
	                    lineNode.setAttribute('font-size', lineFontSize);
	                    lineMetrics.maxFontSize = lineFontSize;
	                }
	            }
	            if (lineMetrics) { linesMetrics.push(lineMetrics); }
	            if (i > 0) { lineNode.setAttribute('dy', dy); }
	            // Firefox requires 'x' to be set on the first line when inside a text path
	            if (i > 0 || textPath) { lineNode.setAttribute('x', x); }
	            lineNode.className.baseVal = lineClassName;
	            containerNode.appendChild(lineNode);
	            offset += line.length + 1;      // + 1 = newline character.
	        }
	        // Y Alignment calculation
	        if (namedVerticalAnchor) {
	            if (annotations) {
	                dy = calculateDY(verticalAnchor, linesMetrics, fontSize, lineHeight);
	            } else if (verticalAnchor === 'top') {
	                // A shortcut for top alignment. It does not depend on font-size nor line-height
	                dy = '0.8em';
	            } else {
	                var rh; // remaining height
	                if (lastI > 0) {
	                    rh = parseFloat(lineHeight) || 1;
	                    rh *= lastI;
	                    if (!emRegex.test(lineHeight)) { rh /= fontSize; }
	                } else {
	                    // Single-line text
	                    rh = 0;
	                }
	                switch (verticalAnchor) {
	                    case 'middle':
	                        dy = (0.3 - (rh / 2)) + 'em';
	                        break;
	                    case 'bottom':
	                        dy = (-rh - 0.3) + 'em';
	                        break;
	                }
	            }
	        } else {
	            if (verticalAnchor === 0) {
	                dy = '0em';
	            } else if (verticalAnchor) {
	                dy = verticalAnchor;
	            } else {
	                // No vertical anchor is defined
	                dy = 0;
	                // Backwards compatibility - we change the `y` attribute instead of `dy`.
	                if (this.attr('y') === null) { this.attr('y', annotatedY || '0.8em'); }
	            }
	        }
	        containerNode.firstChild.setAttribute('dy', dy);
	        // Appending lines to the element.
	        this.append(containerNode);
	        return this;
	    };

	    /**
	     * @public
	     * @param {string} name
	     * @returns {Vectorizer}
	     */
	    VPrototype.removeAttr = function(name) {

	        var qualifiedName = V.qualifyAttr(name);
	        var el = this.node;

	        if (qualifiedName.ns) {
	            if (el.hasAttributeNS(qualifiedName.ns, qualifiedName.local)) {
	                el.removeAttributeNS(qualifiedName.ns, qualifiedName.local);
	            }
	        } else if (el.hasAttribute(name)) {
	            el.removeAttribute(name);
	        }
	        return this;
	    };

	    VPrototype.attr = function(name, value) {

	        if (V.isUndefined(name)) {

	            // Return all attributes.
	            var attributes = this.node.attributes;
	            var attrs = {};

	            for (var i = 0; i < attributes.length; i++) {
	                attrs[attributes[i].name] = attributes[i].value;
	            }

	            return attrs;
	        }

	        if (V.isString(name) && V.isUndefined(value)) {
	            return this.node.getAttribute(name);
	        }

	        if (typeof name === 'object') {

	            for (var attrName in name) {
	                if (name.hasOwnProperty(attrName)) {
	                    this.setAttribute(attrName, name[attrName]);
	                }
	            }

	        } else {

	            this.setAttribute(name, value);
	        }

	        return this;
	    };

	    VPrototype.normalizePath = function() {

	        var tagName = this.tagName();
	        if (tagName === 'PATH') {
	            this.attr('d', V.normalizePathData(this.attr('d')));
	        }

	        return this;
	    };

	    VPrototype.remove = function() {

	        if (this.node.parentNode) {
	            this.node.parentNode.removeChild(this.node);
	        }

	        return this;
	    };

	    VPrototype.empty = function() {

	        while (this.node.firstChild) {
	            this.node.removeChild(this.node.firstChild);
	        }

	        return this;
	    };

	    /**
	     * @private
	     * @param {object} attrs
	     * @returns {Vectorizer}
	     */
	    VPrototype.setAttributes = function(attrs) {

	        for (var key in attrs) {
	            if (attrs.hasOwnProperty(key)) {
	                this.setAttribute(key, attrs[key]);
	            }
	        }

	        return this;
	    };

	    VPrototype.append = function(els) {

	        if (!V.isArray(els)) {
	            els = [els];
	        }

	        for (var i = 0, len = els.length; i < len; i++) {
	            this.node.appendChild(V.toNode(els[i])); // lgtm [js/xss-through-dom]
	        }

	        return this;
	    };

	    VPrototype.prepend = function(els) {

	        var child = this.node.firstChild;
	        return child ? V(child).before(els) : this.append(els);
	    };

	    VPrototype.before = function(els) {

	        var node = this.node;
	        var parent = node.parentNode;

	        if (parent) {

	            if (!V.isArray(els)) {
	                els = [els];
	            }

	            for (var i = 0, len = els.length; i < len; i++) {
	                parent.insertBefore(V.toNode(els[i]), node);
	            }
	        }

	        return this;
	    };

	    VPrototype.appendTo = function(node) {
	        V.toNode(node).appendChild(this.node); // lgtm [js/xss-through-dom]
	        return this;
	    };

	    VPrototype.svg = function() {

	        return this.node instanceof window.SVGSVGElement ? this : V(this.node.ownerSVGElement);
	    };

	    VPrototype.tagName = function() {

	        return this.node.tagName.toUpperCase();
	    };

	    VPrototype.defs = function() {
	        var context = this.svg() || this;
	        var defsNode = context.node.getElementsByTagName('defs')[0];
	        if (defsNode) { return V(defsNode); }
	        return V('defs').appendTo(context);
	    };

	    VPrototype.clone = function() {

	        var clone = V(this.node.cloneNode(true/* deep */));
	        // Note that clone inherits also ID. Therefore, we need to change it here.
	        clone.node.id = V.uniqueId();
	        return clone;
	    };

	    VPrototype.findOne = function(selector) {

	        var found = this.node.querySelector(selector);
	        return found ? V(found) : undefined;
	    };

	    VPrototype.find = function(selector) {

	        var vels = [];
	        var nodes = this.node.querySelectorAll(selector);

	        if (nodes) {

	            // Map DOM elements to `V`s.
	            for (var i = 0; i < nodes.length; i++) {
	                vels.push(V(nodes[i]));
	            }
	        }

	        return vels;
	    };

	    // Returns an array of V elements made from children of this.node.
	    VPrototype.children = function() {

	        var children = this.node.childNodes;

	        var outputArray = [];
	        for (var i = 0; i < children.length; i++) {
	            var currentChild = children[i];
	            if (currentChild.nodeType === 1) {
	                outputArray.push(V(children[i]));
	            }
	        }
	        return outputArray;
	    };

	    // Returns the V element from parentNode of this.node.
	    VPrototype.parent = function() {
	        return V(this.node.parentNode) || null;
	    },

	    // Find an index of an element inside its container.
	    VPrototype.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;
	    };

	    VPrototype.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;
	    };

	    // https://jsperf.com/get-common-parent
	    VPrototype.contains = function(el) {

	        var a = this.node;
	        var b = V.toNode(el);
	        var bup = b && b.parentNode;

	        return (a === bup) || !!(bup && bup.nodeType === 1 && (a.compareDocumentPosition(bup) & 16));
	    };

	    // Convert global point into the coordinate space of this element.
	    VPrototype.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);
	    };

	    VPrototype.translateCenterToPoint = function(p) {

	        var bbox = this.getBBox({ target: this.svg() });
	        var center = bbox.center();

	        this.translate(p.x - center.x, p.y - center.y);
	        return this;
	    };

	    // 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.
	    VPrototype.translateAndAutoOrient = function(position, reference, target) {

	        position = new Point(position);
	        reference =  new Point(reference);
	        target || (target = this.svg());

	        // 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 scale = this.scale();
	        this.attr('transform', '');
	        var bbox = this.getBBox({ target: target }).scale(scale.sx, scale.sy);

	        // 1. Translate to origin.
	        var translateToOrigin = V.createSVGTransform();
	        translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2);

	        // 2. Rotate around origin.
	        var rotateAroundOrigin = V.createSVGTransform();
	        var angle = position.angleBetween(reference, position.clone().offset(1, 0));
	        if (angle) { rotateAroundOrigin.setRotate(angle, 0, 0); }

	        // 3. Translate to the `position` + the offset (half my width) towards the `reference` point.
	        var translateFromOrigin = V.createSVGTransform();
	        var finalPosition = position.clone().move(reference, bbox.width / 2);
	        translateFromOrigin.setTranslate(2 * position.x - finalPosition.x, 2 * position.y - finalPosition.y);

	        // 4. Get the current transformation matrix of this node
	        var ctm = this.getTransformToElement(target);

	        // 5. Apply transformations and the scale
	        var transform = V.createSVGTransform();
	        transform.setMatrix(
	            translateFromOrigin.matrix.multiply(
	                rotateAroundOrigin.matrix.multiply(
	                    translateToOrigin.matrix.multiply(
	                        ctm.scale(scale.sx, scale.sy)))));

	        this.attr('transform', V.matrixToTransformString(transform.matrix));

	        return this;
	    };

	    VPrototype.animateAlongPath = function(attrs, path) {

	        path = V.toNode(path);

	        var id = V.ensureId(path);
	        var animateMotion = V('animateMotion', attrs);
	        var mpath = V('mpath', { 'xlink:href': '#' + id });

	        animateMotion.append(mpath);

	        this.append(animateMotion);
	        try {
	            animateMotion.node.beginElement();
	        } catch (e) {
	            // Fallback for IE 9.
	            // Run the animation programmatically if FakeSmile (`http://leunen.me/fakesmile/`) present
	            if (document.documentElement.getAttribute('smiling') === 'fake') {
	                /* global getTargets:true, Animator:true, animators:true id2anim:true */
	                // 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();
	                }
	            }
	        }
	        return this;
	    };


	    // Split a string into an array of tokens.
	    // https://infra.spec.whatwg.org/#ascii-whitespace
	    var noHTMLWhitespaceRegex = /[^\x20\t\r\n\f]+/g;
	    function getTokenList(str) {
	        if (!V.isString(str)) { return []; }
	        return str.trim().match(noHTMLWhitespaceRegex) || [];
	    }

	    VPrototype.hasClass = function(className) {
	        if (!V.isString(className)) { return false; }
	        return this.node.classList.contains(className.trim());
	    };

	    VPrototype.addClass = function(className) {
	        var ref;

	        (ref = this.node.classList).add.apply(ref, getTokenList(className));
	        return this;
	    };

	    VPrototype.removeClass = function(className) {
	        var ref;

	        (ref = this.node.classList).remove.apply(ref, getTokenList(className));
	        return this;
	    };

	    VPrototype.toggleClass = function(className, toAdd) {
	        var tokens = getTokenList(className);
	        for (var i = 0; i < tokens.length; i++) {
	            this.node.classList.toggle(tokens[i], toAdd);
	        }
	        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).
	    VPrototype.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;
	    };

	    VPrototype.convertToPath = function() {

	        var path = V('path');
	        path.attr(this.attr());
	        var d = this.convertToPathData();
	        if (d) {
	            path.attr('d', d);
	        }
	        return path;
	    };

	    VPrototype.convertToPathData = function() {

	        var tagName = this.tagName();

	        switch (tagName) {
	            case 'PATH':
	                return this.attr('d');
	            case 'LINE':
	                return V.convertLineToPathData(this.node);
	            case 'POLYGON':
	                return V.convertPolygonToPathData(this.node);
	            case 'POLYLINE':
	                return V.convertPolylineToPathData(this.node);
	            case 'ELLIPSE':
	                return V.convertEllipseToPathData(this.node);
	            case 'CIRCLE':
	                return V.convertCircleToPathData(this.node);
	            case 'RECT':
	                return V.convertRectToPathData(this.node);
	        }

	        throw new Error(tagName + ' cannot be converted to PATH.');
	    };

	    V.prototype.toGeometryShape = function() {
	        var x, y, width, height, cx, cy, r, rx, ry, points, d, x1, x2, y1, y2;
	        switch (this.tagName()) {

	            case 'RECT':
	                x = parseFloat(this.attr('x')) || 0;
	                y = parseFloat(this.attr('y')) || 0;
	                width = parseFloat(this.attr('width')) || 0;
	                height = parseFloat(this.attr('height')) || 0;
	                return new Rect(x, y, width, height);

	            case 'CIRCLE':
	                cx = parseFloat(this.attr('cx')) || 0;
	                cy = parseFloat(this.attr('cy')) || 0;
	                r = parseFloat(this.attr('r')) || 0;
	                return new Ellipse({ x: cx, y: cy }, r, r);

	            case 'ELLIPSE':
	                cx = parseFloat(this.attr('cx')) || 0;
	                cy = parseFloat(this.attr('cy')) || 0;
	                rx = parseFloat(this.attr('rx')) || 0;
	                ry = parseFloat(this.attr('ry')) || 0;
	                return new Ellipse({ x: cx, y: cy }, rx, ry);

	            case 'POLYLINE':
	                points = V.getPointsFromSvgNode(this);
	                return new Polyline(points);

	            case 'POLYGON':
	                points = V.getPointsFromSvgNode(this);
	                if (points.length > 1) { points.push(points[0]); }
	                return new Polyline(points);

	            case 'PATH':
	                d = this.attr('d');
	                if (!Path.isDataSupported(d)) { d = V.normalizePathData(d); }
	                return new Path(d);

	            case 'LINE':
	                x1 = parseFloat(this.attr('x1')) || 0;
	                y1 = parseFloat(this.attr('y1')) || 0;
	                x2 = parseFloat(this.attr('x2')) || 0;
	                y2 = parseFloat(this.attr('y2')) || 0;
	                return new Line({ x: x1, y: y1 }, { x: x2, y: y2 });
	        }

	        // Anything else is a rectangle
	        return this.getBBox();
	    };

	    // 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.
	    // 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` coordinate system (the same system as `ref` is in) if
	    // an intersection is found. Returns `undefined` otherwise.
	    VPrototype.findIntersection = function(ref, target) {

	        var svg = this.svg().node;
	        target = target || svg;
	        var bbox = this.getBBox({ target: target });
	        var center = bbox.center();

	        if (!bbox.intersectionWithLineFromCenterToPoint(ref)) { return undefined; }

	        var spot;
	        var tagName = this.tagName();

	        // Little speed up optimization 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 = new 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 = (new 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 = [];

	            var i, sample, gp, centerDistance, refDistance, distance;

	            for (i = 0; i < samples.length; i++) {

	                sample = samples[i];
	                // Convert the sample point in the local coordinate system to the global coordinate system.
	                gp = V.createSVGPoint(sample.x, sample.y);
	                gp = gp.matrixTransform(this.getTransformToElement(target));
	                sample = new Point(gp);
	                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.
	                refDistance = sample.distance(ref) * 1.1;
	                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;
	            });

	            if (closestSamples[0]) {
	                spot = closestSamples[0].sample;
	            }
	        }

	        return spot;
	    };

	    /**
	     * @private
	     * @param {string} name
	     * @param {string} value
	     * @returns {Vectorizer}
	     */
	    VPrototype.setAttribute = function(name, value) {

	        var el = this.node;

	        if (value === null) {
	            this.removeAttr(name);
	            return this;
	        }

	        var qualifiedName = V.qualifyAttr(name);

	        if (qualifiedName.ns) {
	            // Attribute names can be namespaced. E.g. `image` elements
	            // have a `xlink:href` attribute to set the source of the image.
	            el.setAttributeNS(qualifiedName.ns, name, value);
	        } else if (name === 'id') {
	            el.id = value;
	        } else {
	            el.setAttribute(name, value);
	        }

	        return this;
	    };

	    // Create an SVG document element.
	    // If `content` is passed, it will be used as the SVG content of the `<svg>` root element.
	    V.createSvgDocument = function(content) {

	        if (content) {
	            var XMLString = "<svg xmlns=\"" + (ns.svg) + "\" xmlns:xlink=\"" + (ns.xlink) + "\" version=\"" + SVGVersion + "\">" + content + "</svg>";
	            var ref = V.parseXML(XMLString, { async: false });
	            var documentElement = ref.documentElement;
	            return documentElement;
	        }

	        var svg = document.createElementNS(ns.svg, 'svg');
	        svg.setAttributeNS(ns.xmlns, 'xmlns:xlink', ns.xlink);
	        svg.setAttribute('version', SVGVersion);
	        return svg;
	    };

	    V.createSVGStyle = function(stylesheet) {
	        var ref = V('style', { type: 'text/css' }, [
	            V.createCDATASection(stylesheet)
	        ]);
	        var node = ref.node;
	        return node;
	    },

	    V.createCDATASection = function(data) {
	        if ( data === void 0 ) data = '';

	        var xml = document.implementation.createDocument(null, 'xml', null);
	        return xml.createCDATASection(data);
	    };

	    V.idCounter = 0;

	    // A function returning a unique identifier for this client session with every call.
	    V.uniqueId = function() {

	        return 'v-' + (++V.idCounter);
	    };

	    V.toNode = function(el) {

	        return V.isV(el) ? el.node : (el.nodeName && el || el[0]);
	    };

	    V.ensureId = function(node) {

	        node = V.toNode(node);
	        return node.id || (node.id = V.uniqueId());
	    };

	    // 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.
	    V.sanitizeText = function(text) {

	        return (text || '').replace(/ /g, '\u00A0');
	    };

	    V.isUndefined = function(value) {

	        return typeof value === 'undefined';
	    };

	    V.isString = function(value) {

	        return typeof value === 'string';
	    };

	    V.isObject = function(value) {

	        return value && (typeof value === 'object');
	    };

	    V.isArray = Array.isArray;

	    V.parseXML = function(data, opt) {

	        opt = opt || {};

	        var xml;

	        try {
	            var parser = new DOMParser();

	            if (!V.isUndefined(opt.async)) {
	                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;
	    };

	    /**
	     * @param {string} name
	     * @returns {{ns: string|null, local: string}} namespace and attribute name
	     */
	    V.qualifyAttr = function(name) {

	        if (name.indexOf(':') !== -1) {
	            var combinedKey = name.split(':');
	            return {
	                ns: ns[combinedKey[0]],
	                local: combinedKey[1]
	            };
	        }

	        return {
	            ns: null,
	            local: name
	        };
	    };

	    // Note: This regex allows multiple commas as separator which is incorrect in SVG
	    // This regex is used by `split()`, so it doesn't need to use /g
	    V.transformSeparatorRegex = /[ ,]+/;
	    // Note: All following regexes are more restrictive than SVG specification
	    // ReDoS mitigation: Use an anchor at the beginning of the match
	    // ReDoS mitigation: Avoid backtracking (uses `[^()]+` instead of `.*?`)
	    // ReDoS mitigation: Don't match initial `(` inside repeated part
	    // The following regex needs to use /g (= cannot use capturing groups)
	    V.transformRegex = /\b\w+\([^()]+\)/g;
	    // The following regexes need to use capturing groups (= cannot use /g)
	    V.transformFunctionRegex = /\b(\w+)\(([^()]+)\)/;
	    V.transformTranslateRegex = /\btranslate\(([^()]+)\)/;
	    V.transformRotateRegex = /\brotate\(([^()]+)\)/;
	    V.transformScaleRegex = /\bscale\(([^()]+)\)/;

	    V.transformStringToMatrix = function(transform) {

	        // Initialize result matrix as identity matrix
	        var transformationMatrix = V.createSVGMatrix();

	        // Note: Multiple transform functions are allowed in `transform` string
	        // `match()` returns `null` if none found
	        var transformMatches = transform && transform.match(V.transformRegex);
	        if (!transformMatches) {
	            // Return identity matrix
	            return transformationMatrix;
	        }

	        var numMatches = transformMatches.length;
	        for (var i = 0; i < numMatches; i++) {

	            var transformMatch = transformMatches[i];
	            // Use same regex as above, but with capturing groups
	            // `match()` returns values of capturing groups as `[1]`, `[2]`
	            var transformFunctionMatch = transformMatch.match(V.transformFunctionRegex);
	            if (transformFunctionMatch) {

	                var sx = (void 0), sy = (void 0), tx = (void 0), ty = (void 0), angle = (void 0);
	                var ctm = V.createSVGMatrix();
	                var transformFunction = transformFunctionMatch[1].toLowerCase();
	                var args = transformFunctionMatch[2].split(V.transformSeparatorRegex);
	                switch (transformFunction) {

	                    case 'scale':
	                        sx = parseFloat(args[0]);
	                        sy = (args[1] === undefined) ? sx : parseFloat(args[1]);
	                        ctm = ctm.scaleNonUniform(sx, sy);
	                        break;

	                    case 'translate':
	                        tx = parseFloat(args[0]);
	                        ty = parseFloat(args[1]);
	                        ctm = ctm.translate(tx, ty);
	                        break;

	                    case 'rotate':
	                        angle = parseFloat(args[0]);
	                        tx = parseFloat(args[1]) || 0;
	                        ty = parseFloat(args[2]) || 0;
	                        if (tx !== 0 || ty !== 0) {
	                            ctm = ctm.translate(tx, ty).rotate(angle).translate(-tx, -ty);
	                        } else {
	                            ctm = ctm.rotate(angle);
	                        }
	                        break;

	                    case 'skewx':
	                        angle = parseFloat(args[0]);
	                        ctm = ctm.skewX(angle);
	                        break;

	                    case 'skewy':
	                        angle = parseFloat(args[0]);
	                        ctm = ctm.skewY(angle);
	                        break;

	                    case 'matrix':
	                        ctm.a = parseFloat(args[0]);
	                        ctm.b = parseFloat(args[1]);
	                        ctm.c = parseFloat(args[2]);
	                        ctm.d = parseFloat(args[3]);
	                        ctm.e = parseFloat(args[4]);
	                        ctm.f = parseFloat(args[5]);
	                        break;

	                    default:
	                        continue;
	                }

	                // Multiply current transformation into result matrix
	                transformationMatrix = transformationMatrix.multiply(ctm);
	            }

	        }
	        return transformationMatrix;
	    };

	    V.matrixToTransformString = function(matrix) {
	        matrix || (matrix = true);

	        return 'matrix(' +
	            (matrix.a !== undefined ? matrix.a : 1) + ',' +
	            (matrix.b !== undefined ? matrix.b : 0) + ',' +
	            (matrix.c !== undefined ? matrix.c : 0) + ',' +
	            (matrix.d !== undefined ? matrix.d : 1) + ',' +
	            (matrix.e !== undefined ? matrix.e : 0) + ',' +
	            (matrix.f !== undefined ? matrix.f : 0) +
	            ')';
	    };

	    V.parseTransformString = function(transform) {

	        var translate, rotate, scale;

	        if (transform) {

	            var separator = V.transformSeparatorRegex;

	            // Special handling for `transform` with one or more matrix functions
	            if (transform.trim().indexOf('matrix') >= 0) {

	                // Convert EVERYTHING in `transform` string to a matrix
	                // Will combine ALL matrixes * ALL translates * ALL scales * ALL rotates
	                // Note: In non-matrix case, we only take first one of each (if any)
	                var matrix = V.transformStringToMatrix(transform);
	                var decomposedMatrix = V.decomposeMatrix(matrix);

	                // Extract `translate`, `scale`, `rotate` from matrix
	                translate = [decomposedMatrix.translateX, decomposedMatrix.translateY];
	                scale = [decomposedMatrix.scaleX, decomposedMatrix.scaleY];
	                rotate = [decomposedMatrix.rotation];

	                // Rewrite `transform` string in `translate scale rotate` format
	                var transformations = [];
	                if (translate[0] !== 0 || translate[1] !== 0) {
	                    transformations.push('translate(' + translate + ')');
	                }
	                if (scale[0] !== 1 || scale[1] !== 1) {
	                    transformations.push('scale(' + scale + ')');
	                }
	                if (rotate[0] !== 0) {
	                    transformations.push('rotate(' + rotate + ')');
	                }
	                transform = transformations.join(' ');

	            } else {

	                // Extract `translate`, `rotate`, `scale` functions from `transform` string
	                // Note: We only detect the first match of each (if any)
	                // `match()` returns value of capturing group as `[1]`
	                var translateMatch = transform.match(V.transformTranslateRegex);
	                if (translateMatch) {
	                    translate = translateMatch[1].split(separator);
	                }
	                var rotateMatch = transform.match(V.transformRotateRegex);
	                if (rotateMatch) {
	                    rotate = rotateMatch[1].split(separator);
	                }
	                var scaleMatch = transform.match(V.transformScaleRegex);
	                if (scaleMatch) {
	                    scale = scaleMatch[1].split(separator);
	                }
	            }
	        }

	        var sx = (scale && scale[0]) ? parseFloat(scale[0]) : 1;

	        return {
	            value: transform,
	            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
	            }
	        };
	    };

	    V.deltaTransformPoint = function(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 };
	    };

	    V.decomposeMatrix = function(matrix) {

	        // @see https://gist.github.com/2052247

	        // calculate delta transform point
	        var px = V.deltaTransformPoint(matrix, { x: 0, y: 1 });
	        var py = V.deltaTransformPoint(matrix, { x: 1, y: 0 });

	        // calculate skew
	        var skewX = ((180 / PI) * atan2(px.y, px.x) - 90);
	        var skewY = ((180 / PI) * atan2(py.y, py.x));

	        return {

	            translateX: matrix.e,
	            translateY: matrix.f,
	            scaleX: sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
	            scaleY: sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
	            skewX: skewX,
	            skewY: skewY,
	            rotation: skewX // rotation is the same as skew x
	        };
	    };

	    // Return the `scale` transformation from the following equation:
	    // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)`
	    V.matrixToScale = function(matrix) {

	        var a, b, c, d;
	        if (matrix) {
	            a = V.isUndefined(matrix.a) ? 1 : matrix.a;
	            d = V.isUndefined(matrix.d) ? 1 : matrix.d;
	            b = matrix.b;
	            c = matrix.c;
	        } else {
	            a = d = 1;
	        }
	        return {
	            sx: b ? sqrt(a * a + b * b) : a,
	            sy: c ? sqrt(c * c + d * d) : d
	        };
	    };

	    // Return the `rotate` transformation from the following equation:
	    // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)`
	    V.matrixToRotate = function(matrix) {

	        var p = { x: 0, y: 1 };
	        if (matrix) {
	            p = V.deltaTransformPoint(matrix, p);
	        }

	        return {
	            angle: normalizeAngle(toDeg(atan2(p.y, p.x)) - 90)
	        };
	    };

	    // Return the `translate` transformation from the following equation:
	    // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)`
	    V.matrixToTranslate = function(matrix) {

	        return {
	            tx: (matrix && matrix.e) || 0,
	            ty: (matrix && matrix.f) || 0
	        };
	    };

	    V.isV = function(object) {

	        return object instanceof V;
	    };

	    // For backwards compatibility:
	    V.isVElement = V.isV;

	    // Element implements `getBBox()`, `getCTM()` and `getScreenCTM()`
	    // https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement
	    V.isSVGGraphicsElement = function(node) {
	        if (!node) { return false; }
	        node = V.toNode(node);
	        // IE/Edge does not implement SVGGraphicsElement interface, thus check for `getScreenCTM` below
	        return node instanceof SVGElement && typeof node.getScreenCTM === 'function';
	    };

	    var svgDocument = V('svg').node;

	    V.createSVGMatrix = function(matrix) {

	        var svgMatrix = svgDocument.createSVGMatrix();
	        for (var component in matrix) {
	            svgMatrix[component] = matrix[component];
	        }

	        return svgMatrix;
	    };

	    V.createSVGTransform = function(matrix) {

	        if (!V.isUndefined(matrix)) {

	            if (!(matrix instanceof SVGMatrix)) {
	                matrix = V.createSVGMatrix(matrix);
	            }

	            return svgDocument.createSVGTransformFromMatrix(matrix);
	        }

	        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 = min(corner1.x, corner2.x, corner3.x, corner4.x);
	        var maxX = max(corner1.x, corner2.x, corner3.x, corner4.x);
	        var minY = min(corner1.y, corner2.y, corner3.y, corner4.y);
	        var maxY = max(corner1.y, corner2.y, corner3.y, corner4.y);

	        return new Rect(minX, minY, maxX - minX, maxY - minY);
	    };

	    V.transformPoint = function(p, matrix) {

	        return new Point(V.createSVGPoint(p.x, p.y).matrixTransform(matrix));
	    };

	    V.transformLine = function(l, matrix) {

	        return new Line(
	            V.transformPoint(l.start, matrix),
	            V.transformPoint(l.end, matrix)
	        );
	    };

	    V.transformPolyline = function(p, matrix) {

	        var inPoints = (p instanceof Polyline) ? p.points : p;
	        if (!V.isArray(inPoints)) { inPoints = []; }
	        var outPoints = [];
	        for (var i = 0, n = inPoints.length; i < n; i++) { outPoints[i] = V.transformPoint(inPoints[i], matrix); }
	        return new Polyline(outPoints);
	    };

	    // 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 * 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 < PI ? '0' : '1';
	        var c0 = cos(a0);
	        var s0 = sin(a0);
	        var c1 = cos(a1);
	        var s1 = 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 (V.isObject(a[attr]) && V.isObject(b[attr])) {
	                    // `style` stored in `a` is an object.
	                    a[attr] = V.mergeAttrs(a[attr], b[attr]);
	                } else if (V.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 (V.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 || {};

	        var 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 (V.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 (V.isObject(item) && V.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 (V.isObject(item)) {
	                // Previous item was a string, current item is an annotation.
	                compacted.push(batch);
	                batch = item;

	            } else if (V.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) {

	        var found = [];

	        if (annotations) {

	            annotations.forEach(function(annotation) {

	                if (annotation.start < index && index <= annotation.end) {
	                    found.push(annotation);
	                }
	            });
	        }

	        return found;
	    };

	    V.findAnnotationsBetweenIndexes = function(annotations, start, end) {

	        var found = [];

	        if (annotations) {

	            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) {

	            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.convertLineToPathData = function(line) {

	        line = V(line);
	        var d = [
	            'M', line.attr('x1'), line.attr('y1'),
	            'L', line.attr('x2'), line.attr('y2')
	        ].join(' ');
	        return d;
	    };

	    V.convertPolygonToPathData = function(polygon) {

	        var points = V.getPointsFromSvgNode(polygon);
	        if (points.length === 0) { return null; }

	        return V.svgPointsToPath(points) + ' Z';
	    };

	    V.convertPolylineToPathData = function(polyline) {

	        var points = V.getPointsFromSvgNode(polyline);
	        if (points.length === 0) { return null; }

	        return V.svgPointsToPath(points);
	    };

	    V.svgPointsToPath = function(points) {

	        for (var i = 0, n = points.length; i < n; i++) {
	            points[i] = points[i].x + ' ' + points[i].y;
	        }

	        return 'M ' + points.join(' L');
	    };

	    V.getPointsFromSvgNode = function(node) {

	        node = V.toNode(node);
	        var points = [];
	        var nodePoints = node.points;
	        if (nodePoints) {
	            for (var i = 0, n = nodePoints.numberOfItems; i < n; i++) {
	                points.push(nodePoints.getItem(i));
	            }
	        }

	        return points;
	    };

	    V.KAPPA = 0.551784;

	    V.convertCircleToPathData = function(circle) {

	        circle = V(circle);
	        var cx = parseFloat(circle.attr('cx')) || 0;
	        var cy = parseFloat(circle.attr('cy')) || 0;
	        var r = parseFloat(circle.attr('r'));
	        var cd = r * V.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;
	    };

	    V.convertEllipseToPathData = function(ellipse) {

	        ellipse = V(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 * V.KAPPA; // Control distance x.
	        var cdy = ry * V.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;
	    };

	    V.convertRectToPathData = function(rect) {

	        rect = V(rect);

	        return V.rectToPath({
	            x: parseFloat(rect.attr('x')) || 0,
	            y: parseFloat(rect.attr('y')) || 0,
	            width: parseFloat(rect.attr('width')) || 0,
	            height: parseFloat(rect.attr('height')) || 0,
	            rx: parseFloat(rect.attr('rx')) || 0,
	            ry: parseFloat(rect.attr('ry')) || 0
	        });
	    };

	    // 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).
	    V.rectToPath = function(r) {

	        var d;
	        var x = r.x;
	        var y = r.y;
	        var width = r.width;
	        var height = r.height;
	        var topRx = min(r.rx || r['top-rx'] || 0, width / 2);
	        var bottomRx = min(r.rx || r['bottom-rx'] || 0, width / 2);
	        var topRy = min(r.ry || r['top-ry'] || 0, height / 2);
	        var bottomRy = min(r.ry || r['bottom-ry'] || 0, height / 2);

	        if (topRx || bottomRx || topRy || bottomRy) {
	            d = [
	                'M', x, y + topRy,
	                'v', height - topRy - bottomRy,
	                'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy,
	                'h', width - 2 * bottomRx,
	                'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy,
	                'v', -(height - bottomRy - topRy),
	                'a', topRx, topRy, 0, 0, 0, -topRx, -topRy,
	                'h', -(width - 2 * topRx),
	                'a', topRx, topRy, 0, 0, 0, -topRx, topRy,
	                'Z'
	            ];
	        } else {
	            d = [
	                'M', x, y,
	                'H', x + width,
	                'V', y + height,
	                'H', x,
	                'V', y,
	                'Z'
	            ];
	        }

	        return d.join(' ');
	    };

	    // Take a path data string
	    // Return a normalized path data string
	    // If data cannot be parsed, return 'M 0 0'
	    // Adapted from Rappid normalizePath polyfill
	    // Highly inspired by Raphael Library (www.raphael.com)
	    V.normalizePathData = (function() {

	        var spaces = '\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029';
	        var pathCommand = new RegExp('([a-z])[' + spaces + ',]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[' + spaces + ']*,?[' + spaces + ']*)+)', 'ig');
	        var pathValues = new RegExp('(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[' + spaces + ']*,?[' + spaces + ']*', 'ig');

	        var math = Math;
	        var PI = math.PI;
	        var sin = math.sin;
	        var cos = math.cos;
	        var tan = math.tan;
	        var asin = math.asin;
	        var sqrt = math.sqrt;
	        var abs = math.abs;

	        function q2c(x1, y1, ax, ay, x2, y2) {

	            var _13 = 1 / 3;
	            var _23 = 2 / 3;
	            return [(_13 * x1) + (_23 * ax), (_13 * y1) + (_23 * ay), (_13 * x2) + (_23 * ax), (_13 * y2) + (_23 * ay), x2, y2];
	        }

	        function rotate(x, y, rad) {

	            var X = (x * cos(rad)) - (y * sin(rad));
	            var Y = (x * sin(rad)) + (y * cos(rad));
	            return { x: X, y: Y };
	        }

	        function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
	            // for more information of where this math came from visit:
	            // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
	            var _120 = (PI * 120) / 180;
	            var rad = (PI / 180) * (+angle || 0);
	            var res = [];
	            var xy;

	            if (!recursive) {
	                xy = rotate(x1, y1, -rad);
	                x1 = xy.x;
	                y1 = xy.y;

	                xy = rotate(x2, y2, -rad);
	                x2 = xy.x;
	                y2 = xy.y;

	                var x = (x1 - x2) / 2;
	                var y = (y1 - y2) / 2;
	                var h = ((x * x) / (rx * rx)) + ((y * y) / (ry * ry));

	                if (h > 1) {
	                    h = sqrt(h);
	                    rx = h * rx;
	                    ry = h * ry;
	                }

	                var rx2 = rx * rx;
	                var ry2 = ry * ry;

	                var k = ((large_arc_flag == sweep_flag) ? -1 : 1) * sqrt(abs(((rx2 * ry2) - (rx2 * y * y) - (ry2 * x * x)) / ((rx2 * y * y) + (ry2 * x * x))));

	                var cx = ((k * rx * y) / ry) + ((x1 + x2) / 2);
	                var cy = ((k * -ry * x) / rx) + ((y1 + y2) / 2);

	                var f1 = asin(((y1 - cy) / ry).toFixed(9));
	                var f2 = asin(((y2 - cy) / ry).toFixed(9));

	                f1 = ((x1 < cx) ? (PI - f1) : f1);
	                f2 = ((x2 < cx) ? (PI - f2) : f2);

	                if (f1 < 0) { f1 = (PI * 2) + f1; }
	                if (f2 < 0) { f2 = (PI * 2) + f2; }

	                if (sweep_flag && (f1 > f2)) { f1 = f1 - (PI * 2); }
	                if (!sweep_flag && (f2 > f1)) { f2 = f2 - (PI * 2); }

	            } else {
	                f1 = recursive[0];
	                f2 = recursive[1];
	                cx = recursive[2];
	                cy = recursive[3];
	            }

	            var df = f2 - f1;
	            if (abs(df) > _120) {
	                var f2old = f2;
	                var x2old = x2;
	                var y2old = y2;
	                f2 = f1 + (_120 * ((sweep_flag && (f2 > f1)) ? 1 : -1));
	                x2 = cx + (rx * cos(f2));
	                y2 = cy + (ry * sin(f2));
	                res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
	            }

	            df = f2 - f1;

	            var c1 = cos(f1);
	            var s1 = sin(f1);
	            var c2 = cos(f2);
	            var s2 = sin(f2);
	            var t = tan(df / 4);
	            var hx = (4 / 3) * (rx * t);
	            var hy = (4 / 3) * (ry * t);
	            var m1 = [x1, y1];
	            var m2 = [x1 + (hx * s1), y1 - (hy * c1)];
	            var m3 = [x2 + (hx * s2), y2 - (hy * c2)];
	            var m4 = [x2, y2];

	            m2[0] = (2 * m1[0]) - m2[0];
	            m2[1] = (2 * m1[1]) - m2[1];

	            if (recursive) {
	                return [m2, m3, m4].concat(res);
	            } else {
	                res = [m2, m3, m4].concat(res).join().split(',');
	                var newres = [];
	                var ii = res.length;
	                for (var i = 0; i < ii; i++) {
	                    newres[i] = (i % 2) ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
	                }
	                return newres;
	            }
	        }

	        function parsePathString(pathString) {

	            if (!pathString) { return null; }

	            var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 };
	            var data = [];

	            String(pathString).replace(pathCommand, function(a, b, c) {

	                var params = [];
	                var name = b.toLowerCase();
	                c.replace(pathValues, function(a, b) {
	                    if (b) { params.push(+b); }
	                });

	                if ((name === 'm') && (params.length > 2)) {
	                    data.push([b].concat(params.splice(0, 2)));
	                    name = 'l';
	                    b = ((b === 'm') ? 'l' : 'L');
	                }

	                while (params.length >= paramCounts[name]) {
	                    data.push([b].concat(params.splice(0, paramCounts[name])));
	                    if (!paramCounts[name]) { break; }
	                }
	            });

	            return data;
	        }

	        function pathToAbsolute(pathArray) {

	            if (!Array.isArray(pathArray) || !Array.isArray(pathArray && pathArray[0])) { // rough assumption
	                pathArray = parsePathString(pathArray);
	            }

	            // if invalid string, return 'M 0 0'
	            if (!pathArray || !pathArray.length) { return [['M', 0, 0]]; }

	            var res = [];
	            var x = 0;
	            var y = 0;
	            var mx = 0;
	            var my = 0;
	            var start = 0;
	            var pa0;

	            var ii = pathArray.length;
	            for (var i = start; i < ii; i++) {

	                var r = [];
	                res.push(r);

	                var pa = pathArray[i];
	                pa0 = pa[0];

	                if (pa0 != pa0.toUpperCase()) {
	                    r[0] = pa0.toUpperCase();

	                    var jj;
	                    var j;
	                    switch (r[0]) {
	                        case 'A':
	                            r[1] = pa[1];
	                            r[2] = pa[2];
	                            r[3] = pa[3];
	                            r[4] = pa[4];
	                            r[5] = pa[5];
	                            r[6] = +pa[6] + x;
	                            r[7] = +pa[7] + y;
	                            break;

	                        case 'V':
	                            r[1] = +pa[1] + y;
	                            break;

	                        case 'H':
	                            r[1] = +pa[1] + x;
	                            break;

	                        case 'M':
	                            mx = +pa[1] + x;
	                            my = +pa[2] + y;

	                            jj = pa.length;
	                            for (j = 1; j < jj; j++) {
	                                r[j] = +pa[j] + ((j % 2) ? x : y);
	                            }
	                            break;

	                        default:
	                            jj = pa.length;
	                            for (j = 1; j < jj; j++) {
	                                r[j] = +pa[j] + ((j % 2) ? x : y);
	                            }
	                            break;
	                    }
	                } else {
	                    var kk = pa.length;
	                    for (var k = 0; k < kk; k++) {
	                        r[k] = pa[k];
	                    }
	                }

	                switch (r[0]) {
	                    case 'Z':
	                        x = +mx;
	                        y = +my;
	                        break;

	                    case 'H':
	                        x = r[1];
	                        break;

	                    case 'V':
	                        y = r[1];
	                        break;

	                    case 'M':
	                        mx = r[r.length - 2];
	                        my = r[r.length - 1];
	                        x = r[r.length - 2];
	                        y = r[r.length - 1];
	                        break;

	                    default:
	                        x = r[r.length - 2];
	                        y = r[r.length - 1];
	                        break;
	                }
	            }

	            return res;
	        }

	        function normalize(path) {

	            var p = pathToAbsolute(path);
	            var attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null };

	            function processPath(path, d, pcom) {

	                var nx, ny;

	                if (!path) { return ['C', d.x, d.y, d.x, d.y, d.x, d.y]; }

	                if (!(path[0] in { T: 1, Q: 1 })) {
	                    d.qx = null;
	                    d.qy = null;
	                }

	                switch (path[0]) {
	                    case 'M':
	                        d.X = path[1];
	                        d.Y = path[2];
	                        break;

	                    case 'A':
	                        if (parseFloat(path[1]) === 0 || parseFloat(path[2]) === 0) {
	                            // https://www.w3.org/TR/SVG/paths.html#ArcOutOfRangeParameters
	                            // "If either rx or ry is 0, then this arc is treated as a
	                            // straight line segment (a "lineto") joining the endpoints."
	                            path = ['L', path[6], path[7]];
	                        } else {
	                            path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
	                        }
	                        break;

	                    case 'S':
	                        if (pcom === 'C' || pcom === 'S') { // In 'S' case we have to take into account, if the previous command is C/S.
	                            nx = (d.x * 2) - d.bx;          // And reflect the previous
	                            ny = (d.y * 2) - d.by;          // command's control point relative to the current point.
	                        } else {                            // or some else or nothing
	                            nx = d.x;
	                            ny = d.y;
	                        }
	                        path = ['C', nx, ny].concat(path.slice(1));
	                        break;

	                    case 'T':
	                        if (pcom === 'Q' || pcom === 'T') { // In 'T' case we have to take into account, if the previous command is Q/T.
	                            d.qx = (d.x * 2) - d.qx;        // And make a reflection similar
	                            d.qy = (d.y * 2) - d.qy;        // to case 'S'.
	                        } else {                            // or something else or nothing
	                            d.qx = d.x;
	                            d.qy = d.y;
	                        }
	                        path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
	                        break;

	                    case 'Q':
	                        d.qx = path[1];
	                        d.qy = path[2];
	                        path = ['C'].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
	                        break;

	                    case 'H':
	                        path = ['L'].concat(path[1], d.y);
	                        break;

	                    case 'V':
	                        path = ['L'].concat(d.x, path[1]);
	                        break;

	                    case 'L':
	                        break;

	                    case 'Z':
	                        break;
	                }

	                return path;
	            }

	            function fixArc(pp, i) {

	                if (pp[i].length > 7) {

	                    pp[i].shift();
	                    var pi = pp[i];

	                    while (pi.length) {
	                        pcoms[i] = 'A'; // if created multiple 'C's, their original seg is saved
	                        pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6)));
	                    }

	                    pp.splice(i, 1);
	                    ii = p.length;
	                }
	            }

	            var pcoms = []; // path commands of original path p
	            var pfirst = ''; // temporary holder for original path command
	            var pcom = ''; // holder for previous path command of original path

	            var ii = p.length;
	            for (var i = 0; i < ii; i++) {
	                if (p[i]) { pfirst = p[i][0]; } // save current path command

	                if (pfirst !== 'C') { // C is not saved yet, because it may be result of conversion
	                    pcoms[i] = pfirst; // Save current path command
	                    if (i > 0) { pcom = pcoms[i - 1]; } // Get previous path command pcom
	                }

	                p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath

	                if (pcoms[i] !== 'A' && pfirst === 'C') { pcoms[i] = 'C'; } // 'A' is the only command
	                // which may produce multiple 'C's
	                // so we have to make sure that 'C' is also 'C' in original path

	                fixArc(p, i); // fixArc adds also the right amount of 'A's to pcoms

	                var seg = p[i];
	                var seglen = seg.length;

	                attrs.x = seg[seglen - 2];
	                attrs.y = seg[seglen - 1];

	                attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
	                attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
	            }

	            // make sure normalized path data string starts with an M segment
	            if (!p[0][0] || p[0][0] !== 'M') {
	                p.unshift(['M', 0, 0]);
	            }

	            return p;
	        }

	        return function(pathData) {
	            return normalize(pathData).join(',').split(',').join(' ');
	        };
	    })();

	    V.namespace = ns;

	    V.g = g;

	    return V;

	})();

	var config = {
	    // When set to `true` the cell selectors could be defined as CSS selectors.
	    // If not, only JSON Markup selectors are taken into account.
	    // export let useCSSSelectors = true;
	    useCSSSelectors: true,
	    // The class name prefix config is for advanced use only.
	    // Be aware that if you change the prefix, the JointJS CSS will no longer function properly.
	    // export let classNamePrefix = 'joint-';
	    // export let defaultTheme = 'default';
	    classNamePrefix: 'joint-',
	    defaultTheme: 'default',
	    // The maximum delay required for two consecutive touchend events to be interpreted
	    // as a double-tap.
	    doubleTapInterval: 300
	};

	// code is inspired by https://github.com/lodash/lodash

	/* eslint-disable no-case-declarations */
	// -- helper constants
	var argsTag = '[object Arguments]';
	var arrayTag = '[object Array]';
	var boolTag = '[object Boolean]';
	var dateTag = '[object Date]';
	var errorTag = '[object Error]';
	var funcTag = '[object Function]';
	var mapTag = '[object Map]';
	var numberTag = '[object Number]';
	var nullTag = '[object Null]';
	var objectTag = '[object Object]';
	var regexpTag = '[object RegExp]';
	var setTag = '[object Set]';
	var stringTag = '[object String]';
	var symbolTag = '[object Symbol]';
	var undefinedTag = '[object Undefined]';
	var weakMapTag = '[object WeakMap]';
	var arrayBufferTag = '[object ArrayBuffer]';
	var dataViewTag = '[object DataView]';
	var float32Tag = '[object Float32Array]';
	var float64Tag = '[object Float64Array]';
	var int8Tag = '[object Int8Array]';
	var int16Tag = '[object Int16Array]';
	var int32Tag = '[object Int32Array]';
	var uint8Tag = '[object Uint8Array]';
	var uint8ClampedTag = '[object Uint8ClampedArray]';
	var uint16Tag = '[object Uint16Array]';
	var uint32Tag = '[object Uint32Array]';

	var CLONEABLE_TAGS = {};
	CLONEABLE_TAGS[argsTag] = true;
	CLONEABLE_TAGS[arrayTag] = true;
	CLONEABLE_TAGS[arrayBufferTag] = true;
	CLONEABLE_TAGS[dataViewTag] = true;
	CLONEABLE_TAGS[boolTag] = true;
	CLONEABLE_TAGS[dateTag] = true;
	CLONEABLE_TAGS[float32Tag] = true;
	CLONEABLE_TAGS[float64Tag] = true;
	CLONEABLE_TAGS[int8Tag] = true;
	CLONEABLE_TAGS[int16Tag] = true;
	CLONEABLE_TAGS[int32Tag] = true;
	CLONEABLE_TAGS[mapTag] = true;
	CLONEABLE_TAGS[numberTag] = true;
	CLONEABLE_TAGS[objectTag] = true;
	CLONEABLE_TAGS[regexpTag] = true;
	CLONEABLE_TAGS[setTag] = true;
	CLONEABLE_TAGS[stringTag] = true;
	CLONEABLE_TAGS[symbolTag] = true;
	CLONEABLE_TAGS[uint8Tag] = true;
	CLONEABLE_TAGS[uint8ClampedTag] = true;
	CLONEABLE_TAGS[uint16Tag] = true;
	CLONEABLE_TAGS[uint32Tag] = true;
	CLONEABLE_TAGS[errorTag] = false;
	CLONEABLE_TAGS[funcTag] = false;
	CLONEABLE_TAGS[weakMapTag] = false;

	/** Used to compose unicode character classes. */
	var rsAstralRange = '\\ud800-\\udfff';
	var rsComboMarksRange = '\\u0300-\\u036f';
	var reComboHalfMarksRange = '\\ufe20-\\ufe2f';
	var rsComboSymbolsRange = '\\u20d0-\\u20ff';
	var rsComboMarksExtendedRange = '\\u1ab0-\\u1aff';
	var rsComboMarksSupplementRange = '\\u1dc0-\\u1dff';
	var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange + rsComboMarksExtendedRange + rsComboMarksSupplementRange;
	var rsDingbatRange = '\\u2700-\\u27bf';
	var rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff';
	var rsMathOpRange = '\\xac\\xb1\\xd7\\xf7';
	var rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf';
	var rsPunctuationRange = '\\u2000-\\u206f';
	var rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000';
	var rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde';
	var rsVarRange = '\\ufe0e\\ufe0f';
	var rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;

	/** Used to compose unicode capture groups. */
	var rsApos = '[\'\u2019]';
	var rsBreak = "[" + rsBreakRange + "]";
	var rsCombo = "[" + rsComboRange + "]";
	var rsDigit = '\\d';
	var rsDingbat = "[" + rsDingbatRange + "]";
	var rsLower = "[" + rsLowerRange + "]";
	var rsMisc = "[^" + rsAstralRange + (rsBreakRange + rsDigit + rsDingbatRange + rsLowerRange + rsUpperRange) + "]";
	var rsFitz = '\\ud83c[\\udffb-\\udfff]';
	var rsModifier = "(?:" + rsCombo + "|" + rsFitz + ")";
	var rsNonAstral = "[^" + rsAstralRange + "]";
	var rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}';
	var rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]';
	var rsUpper = "[" + rsUpperRange + "]";
	var rsZWJ = '\\u200d';

	/** Used to compose unicode regexes. */
	var rsMiscLower = "(?:" + rsLower + "|" + rsMisc + ")";
	var rsMiscUpper = "(?:" + rsUpper + "|" + rsMisc + ")";
	var rsOptContrLower = "(?:" + rsApos + "(?:d|ll|m|re|s|t|ve))?";
	var rsOptContrUpper = "(?:" + rsApos + "(?:D|LL|M|RE|S|T|VE))?";
	var reOptMod = rsModifier + "?";
	var rsOptVar = "[" + rsVarRange + "]?";
	var rsOptJoin = "(?:" + rsZWJ + "(?:" + ([rsNonAstral, rsRegional, rsSurrPair].join('|')) + ")" + (rsOptVar + reOptMod) + ")*";
	var rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])';
	var rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])';
	var rsSeq = rsOptVar + reOptMod + rsOptJoin;
	var rsEmoji = "(?:" + ([rsDingbat, rsRegional, rsSurrPair].join('|')) + ")" + rsSeq;

	var reUnicodeWords = RegExp([
	    (rsUpper + "?" + rsLower + "+" + rsOptContrLower + "(?=" + ([rsBreak, rsUpper, '$'].join('|')) + ")"),
	    (rsMiscUpper + "+" + rsOptContrUpper + "(?=" + ([rsBreak, rsUpper + rsMiscLower, '$'].join('|')) + ")"),
	    (rsUpper + "?" + rsMiscLower + "+" + rsOptContrLower),
	    (rsUpper + "+" + rsOptContrUpper),
	    rsOrdUpper,
	    rsOrdLower,
	    (rsDigit + "+"),
	    rsEmoji
	].join('|'), 'g');

	var LARGE_ARRAY_SIZE = 200;
	var HASH_UNDEFINED = '__hash_undefined__';

	// Used to match `toStringTag` values of typed arrays
	var reTypedTag = /^\[object (?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)Array\]$/;

	// Used to compose unicode capture groups
	var rsAstral = "[" + rsAstralRange + "]";

	// Used to compose unicode regexes
	var rsNonAstralCombo = "" + rsNonAstral + rsCombo + "?";
	var rsSymbol = "(?:" + ([rsNonAstralCombo, rsCombo, rsRegional, rsSurrPair, rsAstral].join('|')) + ")";

	// Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode)
	var reUnicode = RegExp((rsFitz + "(?=" + rsFitz + ")|" + (rsSymbol + rsSeq)), 'g');

	var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/;
	var reIsPlainProp = /^\w*$/;

	var charCodeOfDot = '.'.charCodeAt(0);
	var reEscapeChar = /\\(\\)?/g;
	var rePropName = RegExp(
	    // Match anything that isn't a dot or bracket.
	    '[^.[\\]]+' + '|' +
	  // Or match property names within brackets.
	  '\\[(?:' +
	    // Match a non-string expression.
	    '([^"\'][^[]*)' + '|' +
	    // Or match strings (supports escaping characters).
	    '(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
	  ')\\]'+ '|' +
	  // Or match "" as the space between consecutive dots or empty brackets.
	  '(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))'
	    , 'g');
	var reIsUint = /^(?:0|[1-9]\d*)$/;

	var hasUnicodeWord = RegExp.prototype.test.bind(
	    /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/
	);

	var MAX_ARRAY_INDEX = 4294967295 - 1;

	/** Used to match words composed of alphanumeric characters. */
	// eslint-disable-next-line no-control-regex
	var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;



	// -- helper functions
	var hasUnicode = function (string) {
	    return reUnicode.test(string);
	};

	var unicodeToArray = function (string) {
	    return string.match(reUnicode) || [];
	};

	var asciiToArray = function (string) {
	    return string.split('');
	};

	var stringToArray = function (string) {
	    return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
	};

	var values = function (object) {
	    if (object == null) {
	        return [];
	    }

	    return keys$1(object).map(function (key) { return object[key]; });
	};

	var keys$1 = function (object) {
	    return isArrayLike(object) ? arrayLikeKeys(object) : Object.keys(Object(object));
	};

	var baseKeys = function (object) {
	    if (!isPrototype(object)) {
	        return Object.keys(object);
	    }
	    var result = [];
	    for (var key in Object(object)) {
	        if (hasOwnProperty.call(object, key) && key != 'constructor') {
	            result.push(key);
	        }
	    }

	    return result;
	};

	var arrayLikeKeys = function (value, inherited) {
	    var isArr = Array.isArray(value);
	    var isArg = !isArr && isObjectLike(value) && getTag(value) === argsTag;
	    var isType = !isArr && !isArg && isTypedArray(value);
	    var skipIndexes = isArr || isArg || isType;
	    var length = value.length;
	    var result = new Array(skipIndexes ? length : 0);
	    var index = skipIndexes ? -1 : length;
	    while (++index < length) {
	        result[index] = "" + index;
	    }
	    for (var key in value) {
	        if ((inherited || hasOwnProperty.call(value, key)) &&
	            !(skipIndexes && (
	                // Safari 9 has enumerable `arguments.length` in strict mode.
	                key === 'length' ||
	                // Skip index properties.
	                isIndex(key, length)
	            ))
	        ) {
	            result.push(key);
	        }
	    }
	    return result;
	};

	var assocIndexOf = function (array, key) {
	    var length = array.length;
	    while (length--) {
	        if (eq(array[length][0], key)) {
	            return length;
	        }
	    }
	    return -1;
	};

	var eq = function (value, other) {
	    return value === other || (value !== value && other !== other);
	};

	var isObjectLike = function (value) {
	    return value != null && typeof value == 'object';
	};

	var isIterateeCall = function (value, index, object) {
	    if (!isObject$1(object)) {
	        return false;
	    }
	    var type = typeof index;

	    var isPossibleIteratee = type == 'number' ?
	        (isArrayLike(object) && index > -1 && index < object.length) :
	        (type == 'string' && index in object);

	    if (isPossibleIteratee) {
	        return eq(object[index], value);
	    }
	    return false;
	};

	var isSet = function (value) {
	    return isObjectLike(value) && getTag(value) == setTag;
	};

	var isMap = function (value) {
	    return isObjectLike(value) && getTag(value) == mapTag;
	};

	var isPrototype = function (value) {
	    var Ctor = value && value.constructor;
	    var proto = (typeof Ctor === 'function' && Ctor.prototype) || Object.prototype;

	    return value === proto;
	};

	var assignValue = function (object, key, value) {
	    var objValue = object[key];
	    if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
	        (value === undefined && !(key in object))) {
	        object[key] = value;
	    }
	};

	var copyObject = function (source, props, object) {
	    var index = -1;
	    var length = props.length;

	    while (++index < length) {
	        var key = props[index];
	        assignValue(object, key, source[key]);
	    }
	    return object;
	};

	var isArrayLike = function (value) {
	    return value != null && typeof value !== 'function' && typeof value.length === 'number' &&
	        value.length > -1 && value.length % 1 === 0;
	};

	var isSymbol = function (value) {
	    return typeof value == 'symbol' ||
	        (isObjectLike(value) && getTag(value) === symbolTag);
	};

	var initCloneArray = function (array) {
	    var length = array.length;
	    var result = new array.constructor(length);

	    if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
	        result.index = array.index;
	        result.input = array.input;
	    }

	    return result;
	};

	var copyArray = function (source, array) {
	    var index = -1;
	    var length = source.length;

	    array || (array = new Array(length));
	    while (++index < length) {
	        array[index] = source[index];
	    }
	    return array;
	};

	var getTag = function (value) {
	    if (value == null) {
	        return value === undefined ? undefinedTag : nullTag;
	    }

	    return Object.prototype.toString.call(value);
	};

	var cloneArrayBuffer = function (arrayBuffer) {
	    var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
	    new Uint8Array(result).set(new Uint8Array(arrayBuffer));
	    return result;
	};

	var cloneTypedArray = function (typedArray, isDeep) {
	    var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
	    return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
	};

	var cloneRegExp = function (regexp) {
	    var result = new regexp.constructor(regexp.source, /\w*$/.exec(regexp));
	    result.lastIndex = regexp.lastIndex;
	    return result;
	};

	var initCloneObject = function (object) {
	    return (typeof object.constructor == 'function' && !isPrototype(object))
	        ? Object.create(Object.getPrototypeOf(object))
	        : {};
	};

	var getSymbols = function (object) {
	    if (object == null) {
	        return [];
	    }

	    object = Object(object);
	    var symbols = Object.getOwnPropertySymbols(object);

	    return symbols.filter(function (symbol) { return propertyIsEnumerable.call(object, symbol); });
	};

	var copySymbols = function (source, object) {
	    return copyObject(source, getSymbols(source), object);
	};

	function cloneDataView(dataView, isDeep) {
	    var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
	    return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
	}

	var initCloneByTag = function (object, tag, isDeep) {
	    var Constructor = object.constructor;
	    switch(tag) {
	        case arrayBufferTag:
	            return cloneArrayBuffer(object, isDeep);
	        case boolTag:
	        case dateTag:
	            return new Constructor(+object);
	        case dataViewTag:
	            return cloneDataView(object, isDeep);
	        case float32Tag:
	        case float64Tag:
	        case int8Tag:
	        case int16Tag:
	        case int32Tag:
	        case uint8Tag:
	        case uint8ClampedTag:
	        case uint16Tag:
	        case uint32Tag:
	            return cloneTypedArray(object, isDeep);
	        case mapTag:
	            return new Constructor(object);
	        case numberTag:
	        case stringTag:
	            return new Constructor(object);
	        case regexpTag:
	            return cloneRegExp(object);
	        case setTag:
	            return new Constructor;
	        case symbolTag:
	            return Symbol.prototype.valueOf ? Object(Symbol.prototype.valueOf.call(object)) : {};
	    }
	};

	var isTypedArray = function (value) {
	    return isObjectLike(value) && reTypedTag.test(getTag(value));
	};

	var getAllKeys = function (object) {
	    var result = Object.keys(object);
	    if(!Array.isArray(object) && object != null) {
	        result.push.apply(result, getSymbols(Object(object)));
	    }

	    return result;
	};

	var getSymbolsIn = function (object) {
	    var result = [];
	    while (object) {
	        result.push.apply(result, getSymbols(object));
	        object = Object.getPrototypeOf(Object(object));
	    }

	    return result;
	};

	var getAllKeysIn = function (object) {
	    var result = [];

	    for (var key in object) {
	        result.push(key);
	    }

	    if (!Array.isArray(object)) {
	        result.push.apply(result, getSymbolsIn(object));
	    }

	    return result;
	};

	var getMapData = function (ref, key) {
	    var __data__ = ref.__data__;

	    var data = __data__;
	    return isKeyable(key)
	        ? data[typeof key === 'string' ? 'string' : 'hash']
	        : data.map;
	};

	var equalObjects = function (object, other, equalFunc, stack) {
	    var objProps = getAllKeys(object);
	    var objLength = objProps.length;
	    var othProps = getAllKeys(other);
	    var othLength = othProps.length;

	    if (objLength != othLength) {
	        return false;
	    }
	    var key;
	    var index = objLength;
	    while (index--) {
	        key = objProps[index];
	        if (!(hasOwnProperty.call(other, key))) {
	            return false;
	        }
	    }

	    var objStacked = stack.get(object);
	    var othStacked = stack.get(other);
	    if (objStacked && othStacked) {
	        return objStacked == other && othStacked == object;
	    }
	    var result = true;
	    stack.set(object, other);
	    stack.set(other, object);

	    var compared;
	    var skipCtor;

	    while (++index < objLength) {
	        key = objProps[index];
	        var objValue = object[key];
	        var othValue = other[key];

	        if (!(compared === undefined
	            ? (objValue === othValue || equalFunc(objValue, othValue, stack))
	            : compared
	        )) {
	            result = false;
	            break;
	        }
	        skipCtor || (skipCtor = key == 'constructor');
	    }

	    if (result && !skipCtor) {
	        var objCtor = object.constructor;
	        var othCtor = other.constructor;

	        if (objCtor != othCtor &&
	        ('constructor' in object && 'constructor' in other) &&
	        !(typeof objCtor === 'function' && objCtor instanceof objCtor &&
	            typeof othCtor === 'function' && othCtor instanceof othCtor)) {
	            result = false;
	        }
	    }
	    stack['delete'](object);
	    stack['delete'](other);
	    return result;
	};

	var baseIsEqual = function (value, other, stack) {
	    if (value === other) {
	        return true;
	    }
	    if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
	        return value !== value && other !== other;
	    }

	    return baseIsEqualDeep(value, other, baseIsEqual, stack);
	};

	var baseIsEqualDeep = function (object, other, equalFunc, stack) {
	    var objIsArr = Array.isArray(object);
	    var othIsArr = Array.isArray(other);
	    var objTag = objIsArr ? arrayTag : getTag(object);
	    var othTag = othIsArr ? arrayTag : getTag(other);

	    objTag = objTag == argsTag ? objectTag : objTag;
	    othTag = othTag == argsTag ? objectTag : othTag;

	    var objIsObj = objTag == objectTag;
	    var othIsObj = othTag == objectTag;
	    var isSameTag = objTag == othTag;

	    if (isSameTag && !objIsObj) {
	        stack || (stack = new Stack);
	        return (objIsArr || isTypedArray(object))
	            ? equalArrays(object, other, false, equalFunc, stack)
	            : equalByTag(object, other, objTag, equalFunc, stack);
	    }

	    var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__');
	    var othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');

	    if (objIsWrapped || othIsWrapped) {
	        var objUnwrapped = objIsWrapped ? object.value() : object;
	        var othUnwrapped = othIsWrapped ? other.value() : other;

	        stack || (stack = new Stack);
	        return equalFunc(objUnwrapped, othUnwrapped, stack);
	    }

	    if (!isSameTag) {
	        return false;
	    }

	    stack || (stack = new Stack);
	    return equalObjects(object, other, equalFunc, stack);
	};

	var equalArrays = function (array, other, compareUnordered, equalFunc, stack) {
	    var isPartial = false;
	    var arrLength = array.length;
	    var othLength = other.length;

	    if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
	        return false;
	    }
	    // Assume cyclic values are equal.
	    var arrStacked = stack.get(array);
	    var othStacked = stack.get(other);
	    if (arrStacked && othStacked) {
	        return arrStacked == other && othStacked == array;
	    }
	    var index = -1;
	    var result = true;
	    var seen = compareUnordered ? new SetCache : undefined;

	    stack.set(array, other);
	    stack.set(other, array);

	    var loop = function () {
	        var compared = (void 0);
	        var arrValue = array[index];
	        var othValue = other[index];

	        if (compared !== undefined) {
	            if (compared) {
	                return;
	            }
	            result = false;
	            return 'break';
	        }

	        if (seen) {
	            if (!some(other, function (othValue, othIndex) {
	                if (!cacheHas(seen, othIndex) &&
	            (arrValue === othValue || equalFunc(arrValue, othValue, stack))) {
	                    return seen.push(othIndex);
	                }
	            })) {
	                result = false;
	                return 'break';
	            }
	        } else if (!(
	            arrValue === othValue ||
	            equalFunc(arrValue, othValue, stack)
	        )) {
	            result = false;
	            return 'break';
	        }
	    };

	    while (++index < arrLength) {
	      var returned = loop();

	      if ( returned === 'break' ) break;
	    }
	    stack['delete'](array);
	    stack['delete'](other);
	    return result;
	};

	var some = function (array, predicate) {
	    var index = -1;
	    var length = array == null ? 0 : array.length;

	    while (++index < length) {
	        if (predicate(array[index], index, array)) {
	            return true;
	        }
	    }
	    return false;
	};

	var cacheHas = function (cache, key) {
	    return cache.has(key);
	};

	var compareArrayBufferTag = function (object, other, equalFunc, stack) {
	    if ((object.byteLength != other.byteLength) ||
	                !equalFunc(new Uint8Array(object), new Uint8Array(other), stack)) {
	        return false;
	    }
	    return true;
	};

	var equalByTag = function (object, other, tag, equalFunc, stack) {

	    switch (tag) {
	        case dataViewTag:
	            if ((object.byteLength != other.byteLength) ||
	                (object.byteOffset != other.byteOffset)) {
	                return false;
	            }
	            object = object.buffer;
	            other = other.buffer;
	            return compareArrayBufferTag(object, other, equalFunc, stack);
	        case arrayBufferTag:
	            return compareArrayBufferTag(object, other, equalFunc, stack);
	        case boolTag:
	        case dateTag:
	        case numberTag:
	            return eq(+object, +other);
	        case errorTag:
	            return object.name == other.name && object.message == other.message;
	        case regexpTag:
	        case stringTag:
	            return object == ("" + other);
	        case mapTag:
	            var convert = mapToArray;
	        // Intentional fallthrough
	        // eslint-disable-next-line no-fallthrough
	        case setTag:
	            convert || (convert = setToArray);

	            if (object.size != other.size) {
	                return false;
	            }
	            // Assume cyclic values are equal.
	            var stacked = stack.get(object);
	            if (stacked) {
	                return stacked == other;
	            }

	            // Recursively compare objects (susceptible to call stack limits).
	            stack.set(object, other);
	            var result = equalArrays(convert(object), convert(other), true, equalFunc, stack);
	            stack['delete'](object);
	            return result;
	        case symbolTag:
	            return Symbol.prototype.valueOf.call(object) == Symbol.prototype.valueOf.call(other);
	    }

	    return false;
	};

	var mapToArray = function (map) {
	    var index = -1;
	    var result = Array(map.size);

	    map.forEach(function (value, key) {
	        result[++index] = [key, value];
	    });
	    return result;
	};

	var setToArray = function (set) {
	    var index = -1;
	    var result = new Array(set.size);

	    set.forEach(function (value) {
	        result[++index] = value;
	    });
	    return result;
	};

	var isKey = function (value, object) {
	    if (Array.isArray(value)) {
	        return false;
	    }
	    var type = typeof value;
	    if (type === 'number' || type === 'boolean' || value == null || isSymbol(value)) {
	        return true;
	    }
	    return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
	    (object != null && value in Object(object));
	};

	var stringToPath = function (string) {
	    var result = [];
	    if (string.charCodeAt(0) === charCodeOfDot) {
	        result.push('');
	    }
	    string.replace(rePropName, function (match, expression, quote, subString) {
	        var key = match;
	        if (quote) {
	            key = subString.replace(reEscapeChar, '$1');
	        }
	        else if (expression) {
	            key = expression.trim();
	        }
	        result.push(key);
	    });
	    return result;
	};

	var castPath = function (path, object) {
	    if (Array.isArray(path)) {
	        return path;
	    }

	    return isKey(path, object) ? [path] : stringToPath(("" + path));
	};

	var get$1 = function (object, path) {
	    path = castPath(path, object);

	    var index = 0;
	    var length = path.length;

	    while (object != null && index < length) {
	        object = object[toKey(path[index])];
	        index++;
	    }

	    return (index && index == length) ? object : undefined;
	};

	function compareAscending(value, other) {
	    if (value !== other) {
	        var valIsDefined = value !== undefined;
	        var valIsNull = value === null;
	        var valIsReflexive = value === value;
	        var valIsSymbol = isSymbol(value);

	        var othIsDefined = other !== undefined;
	        var othIsNull = other === null;
	        var othIsReflexive = other === other;
	        var othIsSymbol = isSymbol(other);

	        if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
	            (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
	            (valIsNull && othIsDefined && othIsReflexive) ||
	            (!valIsDefined && othIsReflexive) ||
	            !valIsReflexive) {
	            return 1;
	        }
	        if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
	            (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
	            (othIsNull && valIsDefined && valIsReflexive) ||
	            (!othIsDefined && valIsReflexive) ||
	            !othIsReflexive) {
	            return -1;
	        }
	    }
	    return 0;
	}

	function compareMultiple(object, other, orders) {
	    var index = -1;
	    var objCriteria = object.criteria;
	    var othCriteria = other.criteria;
	    var length = objCriteria.length;
	    var ordersLength = orders.length;

	    while (++index < length) {
	        var order = index < ordersLength ? orders[index] : null;
	        var cmpFn = (order && typeof order === 'function') ? order : compareAscending;
	        var result = cmpFn(objCriteria[index], othCriteria[index]);
	        if (result) {
	            if (order && typeof order !== 'function') {
	                return result * (order == 'desc' ? -1 : 1);
	            }
	            return result;
	        }
	    }

	    return object.index - other.index;
	}

	var diff = function (array, values) {
	    var includes = function (array, value) {
	        var length = array == null ? 0 : array.length;
	        return !!length && array.indexOf(value) > -1;
	    };
	    var isCommon = true;
	    var result = [];
	    var valuesLength = values.length;

	    if (!array.length) {
	        return result;
	    }

	    if (values.length >= LARGE_ARRAY_SIZE) {
	        includes = function (cache, key) { return cache.has(key); };
	        isCommon = false;
	        values = new SetCache(values);
	    }

	    outer:
	    for (var key in array) {
	        var value = array[key];
	        var computed = value;

	        value = (value !== 0) ? value : 0;
	        if (isCommon && computed === computed) {
	            var valuesIndex = valuesLength;
	            while (valuesIndex--) {
	                if (values[valuesIndex] === computed) {
	                    continue outer;
	                }
	            }
	            result.push(value);
	        }
	        else if (!includes(values, computed)) {
	            result.push(value);
	        }
	    }

	    return result;
	};

	var intersect = function (arrays) {
	    var includes = function (array, value) {
	        var length = array == null ? 0 : array.length;
	        return !!length && array.indexOf(value) > -1;
	    };
	    var cacheHas = function (cache, key) { return cache.has(key); };
	    var length = arrays[0].length;
	    var othLength = arrays.length;
	    var caches = new Array(othLength);
	    var result = [];

	    var array;
	    var maxLength = Infinity;
	    var othIndex = othLength;

	    while (othIndex--) {
	        array = arrays[othIndex];

	        maxLength = Math.min(array.length, maxLength);
	        caches[othIndex] = length >= 120 && array.length >= 120
	            ? new SetCache(othIndex && array)
	            : undefined;
	    }
	    array = arrays[0];

	    var index = -1;
	    var seen = caches[0];

	    outer:
	    while (++index < length && result.length < maxLength) {
	        var value = array[index];
	        var computed = value;

	        value = (value !== 0) ? value : 0;
	        if (!(seen
	            ? cacheHas(seen, computed)
	            : includes(result, computed)
	        )) {
	            othIndex = othLength;
	            while (--othIndex) {
	                var cache = caches[othIndex];
	                if (!(cache
	                    ? cacheHas(cache, computed)
	                    : includes(arrays[othIndex], computed))
	                ) {
	                    continue outer;
	                }
	            }
	            if (seen) {
	                seen.push(computed);
	            }
	            result.push(value);
	        }
	    }
	    return result;
	};

	var toKey = function (value) {
	    if (typeof value === 'string' || isSymbol(value)) {
	        return value;
	    }
	    var result = "" + value;
	    return (result == '0' && (1 / value) == -Infinity) ? '-0' : result;
	};

	var baseClone = function (value, isDeep, isFlat, isFull, customizer, key, object, stack) {
	    if ( isDeep === void 0 ) isDeep = false;
	    if ( isFlat === void 0 ) isFlat = false;
	    if ( isFull === void 0 ) isFull = true;

	    var result;

	    if (customizer) {
	        result = object ? customizer(value, key, object, stack) : customizer(value);
	    }

	    if (result !== undefined) {
	        return result;
	    }

	    if (!isObject$1(value)) {
	        return value;
	    }

	    var isArr = Array.isArray(value);
	    var tag = getTag(value);

	    if (isArr) {
	        result = initCloneArray(value);

	        if (!isDeep) {
	            return copyArray(value, result);
	        }
	    } else {
	        var isFunc = typeof value === 'function';

	        if (tag === objectTag || tag === argsTag || (isFunc && !object)) {
	            result = (isFlat || isFunc) ? {} : initCloneObject(value);
	            if (!isDeep) {
	                return isFlat ?
	                    copySymbolsIn(value, copyObject(value, Object.keys(value), result)) :
	                    copySymbols(value, Object.assign(result, value));
	            }
	        } else {
	            if (isFunc || !CLONEABLE_TAGS[tag]) {
	                return object ? value : {};
	            }
	            result = initCloneByTag(value, tag, isDeep);
	        }
	    }

	    stack || (stack = new Stack);
	    var stacked = stack.get(value);

	    if (stacked) {
	        return stacked;
	    }

	    stack.set(value, result);

	    if (isMap(value)) {
	        value.forEach(function (subValue, key) {
	            result.set(key, baseClone(subValue, isDeep, isFlat, isFull, customizer, key, value, stack));
	        });

	        return result;
	    }

	    if (isSet(value)) {
	        value.forEach(function (subValue) {
	            result.add(baseClone(subValue, isDeep, isFlat, isFull, customizer, subValue, value, stack));
	        });

	        return result;
	    }

	    if(isTypedArray(value)) {
	        return result;
	    }

	    var keysFunc = isFull
	        ? (isFlat ? getAllKeysIn : getAllKeys)
	        : (isFlat ? keysIn : keys$1);

	    var props =  isArr ? undefined : keysFunc(value);

	    (props || value).forEach(function (subValue, key) {
	        if (props) {
	            key = subValue;
	            subValue = value[key];
	        }

	        assignValue(result, key, baseClone(subValue, isDeep, isFlat, isFull, customizer, key, value, stack));
	    });

	    return result;
	};

	var copySymbolsIn = function (source, object) {
	    return copyObject(source, getSymbolsIn(source), object);
	};

	var parent = function (object, path) {
	    return path.length < 2 ? object : get$1(object, path.slice(0, -1));
	};

	var set$1 = function (object, path, value) {
	    if (!isObject$1(object)) {
	        return object;
	    }
	    path = castPath(path, object);

	    var length = path.length;
	    var lastIndex = length - 1;

	    var index = -1;
	    var nested = object;

	    while (nested != null && ++index < length) {
	        var key = toKey(path[index]);
	        var newValue = value;

	        if (index != lastIndex) {
	            var objValue = nested[key];
	            newValue = undefined;
	            if (newValue === undefined) {
	                newValue = isObject$1(objValue)
	                    ? objValue
	                    : (isIndex(path[index + 1]) ? [] : {});
	            }
	        }
	        assignValue(nested, key, newValue);
	        nested = nested[key];
	    }
	    return object;
	};

	var isIndex = function (value, length) {
	    var type = typeof value;
	    length = length == null ? Number.MAX_SAFE_INTEGER : length;

	    return !!length &&
	    (type === 'number' ||
	        (type !== 'symbol' && reIsUint.test(value))) &&
	        (value > -1 && value % 1 == 0 && value < length);
	};

	var unset = function (object, path) {
	    path = castPath(path, object);
	    object = parent(object, path);
	    var lastSegment = path[path.length - 1];
	    return object == null || delete object[toKey(lastSegment)];
	};

	var isKeyable = function (value) {
	    var type = typeof value;
	    return (type === 'string' || type === 'number' || type === 'symbol' || type === 'boolean')
	        ? (value !== '__proto__')
	        : (value === null);
	};

	var keysIn = function (object) {
	    var result = [];
	    for (var key in object) {
	        result.push(key);
	    }
	    return result;
	};

	var toPlainObject = function (value) {
	    value = Object(value);
	    var result = {};
	    for (var key in value) {
	        result[key] = value[key];
	    }
	    return result;
	};

	var safeGet = function (object, key) {
	    if (key === 'constructor' && typeof object[key] === 'function') {
	        return;
	    }

	    if (key == '__proto__') {
	        return;
	    }

	    return object[key];
	};

	function createAssigner(assigner, isMerge) {
	    if ( isMerge === void 0 ) isMerge = false;

	    return function (object) {
	        var sources = [], len = arguments.length - 1;
	        while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ];

	        var index = -1;
	        var length = sources.length;
	        var customizer = length > 1 ? sources[length - 1] : undefined;
	        var guard = length > 2 ? sources[2] : undefined;

	        customizer = (assigner.length > 3 && typeof customizer === 'function')
	            ? (length--, customizer)
	            : isMerge ? function (a, b) {
	                if (Array.isArray(a) && !Array.isArray(b)) {
	                    return b;
	                }
	            } : undefined;

	        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
	            customizer = length < 3 ? undefined : customizer;
	            length = 1;
	        }
	        object = Object(object);
	        while (++index < length) {
	            var source = sources[index];
	            if (source) {
	                assigner(object, source, index, customizer);
	            }
	        }
	        return object;
	    };
	}

	var baseMerge = function (object, source, srcIndex, customizer, stack) {
	    if (object === source) {
	        return;
	    }

	    forIn(source, function (srcValue, key) {
	        if (isObject$1(srcValue)) {
	            stack || (stack = new Stack);
	            baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
	        } else {
	            var newValue = customizer
	                ? customizer(object[key], srcValue, ("" + key), object, source, stack)
	                : undefined;

	            if (newValue === undefined) {
	                newValue = srcValue;
	            }

	            assignMergeValue(object, key, newValue);
	        }
	    }, keysIn);
	};

	var baseMergeDeep = function (object, source, key, srcIndex, mergeFunc, customizer, stack) {
	    var objValue = safeGet(object, key);
	    var srcValue = safeGet(source, key);
	    var stacked = stack.get(srcValue);

	    if (stacked) {
	        assignMergeValue(object, key, stacked);
	        return;
	    }

	    var newValue = customizer
	        ? customizer(objValue, srcValue, ("" + key), object, source, stack)
	        : undefined;

	    var isCommon = newValue === undefined;

	    if (isCommon) {
	        var isArr = Array.isArray(srcValue);
	        var isTyped = !isArr && isTypedArray(srcValue);

	        newValue = srcValue;
	        if (isArr || isTyped) {
	            if (Array.isArray(objValue)) {
	                newValue = objValue;
	            }
	            else if (isObjectLike(objValue) && isArrayLike(objValue)) {
	                newValue = copyArray(objValue);
	            }
	            else if (isTyped) {
	                isCommon = false;
	                newValue = cloneTypedArray(srcValue, true);
	            }
	            else {
	                newValue = [];
	            }
	        }
	        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
	            newValue = objValue;
	            if (isArguments(objValue)) {
	                newValue = toPlainObject(objValue);
	            }
	            else if (typeof objValue === 'function' || !isObject$1(objValue)) {
	                newValue = initCloneObject(srcValue);
	            }
	        }
	        else {
	            isCommon = false;
	        }
	    }
	    if (isCommon) {
	    // Recursively merge objects and arrays (susceptible to call stack limits).
	        stack.set(srcValue, newValue);
	        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
	        stack['delete'](srcValue);
	    }
	    assignMergeValue(object, key, newValue);
	};

	var assignMergeValue = function (object, key, value) {
	    if ((value !== undefined && !eq(object[key], value)) ||
	        (value === undefined && !(key in object))) {
	        assignValue(object, key, value);
	    }
	};

	function baseFor(object, iteratee, keysFunc) {
	    var iterable = Object(object);
	    var props = keysFunc(object);
	    var length = props.length;
	    var index = -1;

	    while (length--) {
	        var key = props[++index];
	        if (iteratee(iterable[key], key, iterable) === false) {
	            break;
	        }
	    }
	    return object;
	}

	var baseForOwn = function (object, iteratee) {
	    return object && baseFor(object, iteratee, keys$1);
	};

	var baseEach = function (collection, iteratee) {
	    if (collection == null) {
	        return collection;
	    }
	    if (!isArrayLike(collection)) {
	        return baseForOwn(collection, iteratee);
	    }
	    var length = collection.length;
	    var iterable = Object(collection);
	    var index = -1;

	    while (++index < length) {
	        if (iteratee(iterable[index], index, iterable) === false) {
	            break;
	        }
	    }
	    return collection;
	};

	function last(array) {
	    var length = array == null ? 0 : array.length;
	    return length ? array[length - 1] : undefined;
	}

	var createSet = (Set && (1 / setToArray(new Set([undefined,-0]))[1]) == 1 / 0)
	    ? function (values) { return new Set(values); }
	    : function () {};

	function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
	    if (isObject$1(objValue) && isObject$1(srcValue)) {
	    // Recursively merge objects and arrays (susceptible to call stack limits).
	        stack.set(srcValue, objValue);
	        baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
	        stack['delete'](srcValue);
	    }
	    return objValue;
	}

	function baseOrderBy(collection, iteratees, orders) {
	    if (iteratees.length) {
	        iteratees = iteratees.map(function (iteratee) {
	            if (Array.isArray(iteratee)) {
	                return function (value) { return get$1(value, iteratee.length === 1 ? iteratee[0] : iteratee); };
	            }

	            return iteratee;
	        });
	    } else {
	        iteratees = [function (value) { return value; }];
	    }

	    var criteriaIndex = -1;
	    var eachIndex = -1;

	    var result = isArrayLike(collection) ? new Array(collection.length) : [];

	    baseEach(collection, function (value) {
	        var criteria = iteratees.map(function (iteratee) { return iteratee(value); });

	        result[++eachIndex] = {
	            criteria: criteria,
	            index: ++criteriaIndex,
	            value: value
	        };
	    });

	    return baseSortBy(result, function (object, other) { return compareMultiple(object, other, orders); });
	}

	function baseSortBy(array, comparer) {
	    var length = array.length;

	    array.sort(comparer);
	    while (length--) {
	        array[length] = array[length].value;
	    }
	    return array;
	}

	function isStrictComparable(value) {
	    return value === value && !isObject$1(value);
	}

	function matchesStrictComparable(key, srcValue) {
	    return function (object) {
	        if (object == null) {
	            return false;
	        }
	        return object[key] === srcValue &&
	            (srcValue !== undefined || (key in Object(object)));
	    };
	}

	function hasIn(object, path) {
	    return object != null && hasPath(object, path, baseHasIn);
	}

	function baseMatchesProperty(path, srcValue) {
	    if (isKey(path) && isStrictComparable(srcValue)) {
	        return matchesStrictComparable(toKey(path), srcValue);
	    }
	    return function (object) {
	        var objValue = get$1(object, path);
	        return (objValue === undefined && objValue === srcValue)
	            ? hasIn(object, path)
	            : baseIsEqual(srcValue, objValue);
	    };
	}

	function baseMatches(source) {
	    var matchData = getMatchData(source);
	    if (matchData.length === 1 && matchData[0][2]) {
	        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
	    }
	    return function (object) { return object === source || baseIsMatch(object, source, matchData); };
	}

	function getMatchData(object) {
	    var result = keys$1(object);
	    var length = result.length;

	    while (length--) {
	        var key = result[length];
	        var value = object[key];
	        result[length] = [key, value, isStrictComparable(value)];
	    }
	    return result;
	}

	function baseIsMatch(object, source, matchData, customizer) {
	    var index = matchData.length;
	    var length = index;
	    var noCustomizer = !customizer;

	    if (object == null) {
	        return !length;
	    }
	    var data;
	    var result;
	    object = Object(object);
	    while (index--) {
	        data = matchData[index];
	        if ((noCustomizer && data[2])
	            ? data[1] !== object[data[0]]
	            : !(data[0] in object)
	        ) {
	            return false;
	        }
	    }
	    while (++index < length) {
	        data = matchData[index];
	        var key = data[0];
	        var objValue = object[key];
	        var srcValue = data[1];

	        if (noCustomizer && data[2]) {
	            if (objValue === undefined && !(key in object)) {
	                return false;
	            }
	        } else {
	            var stack = new Stack;
	            if (customizer) {
	                result = customizer(objValue, srcValue, key, object, source, stack);
	            }
	            if (!(result === undefined
	                ? baseIsEqual(srcValue, objValue, stack)
	                : result
	            )) {
	                return false;
	            }
	        }
	    }
	    return true;
	}

	function property(path) {
	    return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
	}

	function baseProperty(key) {
	    return function (object) { return object == null ? undefined : object[key]; };
	}

	function basePropertyDeep(path) {
	    return function (object) { return get$1(object, path); };
	}

	function baseIteratee(value) {
	    if (typeof value == 'function') {
	        return value;
	    }
	    if (value == null) {
	        return function (val) { return val; };
	    }
	    if (typeof value == 'object') {
	        return Array.isArray(value)
	            ? baseMatchesProperty(value[0], value[1])
	            : baseMatches(value);
	    }
	    return property(value);
	}

	function getIteratee() {
	    var result = baseIteratee;
	    return arguments.length ? result(arguments[0], arguments[1]) : result;
	}

	var arrayReduce = function (array, iteratee, accumulator, initAccum) {
	    var index = -1;
	    var length = array == null ? 0 : array.length;

	    if (initAccum && length) {
	        accumulator = array[++index];
	    }
	    while (++index < length) {
	        accumulator = iteratee(accumulator, array[index], index, array);
	    }
	    return accumulator;
	};

	var baseReduce = function (collection, iteratee, accumulator, initAccum, eachFunc) {
	    eachFunc(collection, function (value, index, collection) {
	        accumulator = initAccum
	            ? (initAccum = false, value)
	            : iteratee(accumulator, value, index, collection);
	    });
	    return accumulator;
	};

	function reduce(collection, iteratee, accumulator) {
	    var func = Array.isArray(collection) ? arrayReduce : baseReduce;
	    var initAccum = arguments.length < 3;
	    return func(collection, iteratee, accumulator, initAccum, baseEach);
	}

	var isFlattenable = function (value) {
	    return Array.isArray(value) || isArguments(value) ||
	    !!(value && value[Symbol.isConcatSpreadable]);
	};

	function baseFlatten(array, depth, predicate, isStrict, result) {
	    var index = -1;
	    var length = array.length;

	    predicate || (predicate = isFlattenable);
	    result || (result = []);

	    while (++index < length) {
	        var value = array[index];
	        if (depth > 0 && predicate(value)) {
	            if (depth > 1) {
	                // Recursively flatten arrays (susceptible to call stack limits).
	                baseFlatten(value, depth - 1, predicate, isStrict, result);
	            } else {
	                result.push.apply(result, value);
	            }
	        } else if (!isStrict) {
	            result[result.length] = value;
	        }
	    }
	    return result;
	}

	var isArguments = function (value) {
	    return isObjectLike(value) && getTag(value) == '[object Arguments]';
	};

	var basePick = function (object, paths) {
	    return basePickBy(object, paths, function (value, path) { return hasIn(object, path); });
	};

	var basePickBy = function (object, paths, predicate) {
	    var index = -1;
	    var length = paths.length;
	    var result = {};

	    while (++index < length) {
	        var path = paths[index];
	        var value = get$1(object, path);
	        if (predicate(value, path)) {
	            set$1(result, castPath(path, object), value);
	        }
	    }
	    return result;
	};

	var isLength = function (value) {
	    return typeof value == 'number' &&
	        value > -1 && value % 1 == 0 && value <= Number.MAX_SAFE_INTEGER;
	};

	var baseHasIn = function (object, key) {
	    return object != null && key in Object(object);
	};

	var hasPath = function (object, path, hasFunc) {
	    path = castPath(path, object);

	    var index = -1,
	        length = path.length,
	        result = false;

	    while (++index < length) {
	        var key = toKey(path[index]);
	        if (!(result = object != null && hasFunc(object, key))) {
	            break;
	        }
	        object = object[key];
	    }
	    if (result || ++index != length) {
	        return result;
	    }
	    length = object == null ? 0 : object.length;
	    return !!length && isLength(length) && isIndex(key, length) &&
	        (Array.isArray(object) || isArguments(object));
	};

	var asciiWords = function (string) {
	    return string.match(reAsciiWord);
	};

	var unicodeWords = function (string) {
	    return string.match(reUnicodeWords);
	};

	var words = function (string, pattern) {
	    if (pattern === undefined) {
	        var result = hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string);
	        return result || [];
	    }
	    return string.match(pattern) || [];
	};

	var castSlice = function (array, start, end) {
	    var length = array.length;
	    end = end === undefined ? length : end;
	    return (!start && end >= length) ? array : array.slice(start, end);
	};

	var upperFirst = createCaseFirst('toUpperCase');

	function createCaseFirst(methodName) {
	    return function (string) {
	        if (!string) {
	            return '';
	        }

	        var strSymbols = hasUnicode(string)
	            ? stringToArray(string)
	            : undefined;

	        var chr = strSymbols
	            ? strSymbols[0]
	            : string[0];

	        var trailing = strSymbols
	            ? castSlice(strSymbols, 1).join('')
	            : string.slice(1);

	        return chr[methodName]() + trailing;
	    };
	}

	// -- helper classes
	var Stack = function Stack(entries) {
	      var data = this.__data__ = new ListCache(entries);
	      this.size = data.size;
	  };

	  Stack.prototype.clear = function clear () {
	      this.__data__ = new ListCache;
	      this.size = 0;
	  };

	  Stack.prototype.delete = function delete$1 (key) {
	      var data = this.__data__;
	      var result = data['delete'](key);

	      this.size = data.size;
	      return result;
	  };

	  Stack.prototype.get = function get (key) {
	      return this.__data__.get(key);
	  };

	  Stack.prototype.has = function has (key) {
	      return this.__data__.has(key);
	  };

	  Stack.prototype.set = function set (key, value) {
	      var data = this.__data__;
	      if (data instanceof ListCache) {
	          var pairs = data.__data__;
	          if (pairs.length < LARGE_ARRAY_SIZE - 1) {
	              pairs.push([key, value]);
	              this.size = ++data.size;
	              return this;
	          }
	          data = this.__data__ = new MapCache(pairs);
	      }
	      data.set(key, value);
	      this.size = data.size;
	      return this;
	  };

	var ListCache = function ListCache(entries) {
	      var index = -1;
	      var length = entries == null ? 0 : entries.length;

	      this.clear();
	      while (++index < length) {
	          var entry = entries[index];
	          this.set(entry[0], entry[1]);
	      }
	  };

	  ListCache.prototype.clear = function clear () {
	      this.__data__ = [];
	      this.size = 0;
	  };

	  ListCache.prototype.delete = function delete$2 (key) {
	      var data = this.__data__;
	      var index = assocIndexOf(data, key);

	      if (index < 0) {
	          return false;
	      }
	      var lastIndex = data.length - 1;
	      if (index == lastIndex) {
	          data.pop();
	      } else {
	          data.splice(index, 1);
	      }
	      --this.size;
	      return true;
	  };

	  ListCache.prototype.get = function get (key) {
	      var data = this.__data__;
	      var index = assocIndexOf(data, key);
	      return index < 0 ? undefined : data[index][1];
	  };

	  ListCache.prototype.has = function has (key) {
	      return assocIndexOf(this.__data__, key) > -1;
	  };

	  ListCache.prototype.set = function set (key, value) {
	      var data = this.__data__;
	      var index = assocIndexOf(data, key);

	      if (index < 0) {
	          ++this.size;
	          data.push([key, value]);
	      } else {
	          data[index][1] = value;
	      }
	      return this;
	  };

	var MapCache = function MapCache(entries) {
	      var index = -1;
	      var length = entries == null ? 0 : entries.length;

	      this.clear();
	      while (++index < length) {
	          var entry = entries[index];
	          this.set(entry[0], entry[1]);
	      }
	  };

	  MapCache.prototype.clear = function clear () {
	      this.size = 0;
	      this.__data__ = {
	          'hash': new Hash,
	          'map': new Map,
	          'string': new Hash
	      };
	  };

	  MapCache.prototype.delete = function delete$3 (key) {
	      var result = getMapData(this, key)['delete'](key);
	      this.size -= result ? 1 : 0;
	      return result;
	  };

	  MapCache.prototype.get = function get (key) {
	      return getMapData(this, key).get(key);
	  };

	  MapCache.prototype.has = function has (key) {
	      return getMapData(this, key).has(key);
	  };

	  MapCache.prototype.set = function set (key, value) {
	      var data = getMapData(this, key);
	      var size = data.size;

	      data.set(key, value);
	      this.size += data.size == size ? 0 : 1;
	      return this;
	  };

	var Hash = function Hash(entries) {
	      var index = -1;
	      var length = entries == null ? 0 : entries.length;

	      this.clear();
	      while (++index < length) {
	          var entry = entries[index];
	          this.set(entry[0], entry[1]);
	      }
	  };

	  Hash.prototype.clear = function clear () {
	      this.__data__ = Object.create(null);
	      this.size = 0;
	  };

	  Hash.prototype.delete = function delete$4 (key) {
	      var result = this.has(key) && delete this.__data__[key];
	      this.size -= result ? 1 : 0;
	      return result;
	  };

	  Hash.prototype.get = function get (key) {
	      var data = this.__data__;
	      var result = data[key];
	      return result === HASH_UNDEFINED ? undefined : result;
	  };

	  Hash.prototype.has = function has (key) {
	      var data = this.__data__;
	      return data[key] !== undefined;
	  };

	  Hash.prototype.set = function set (key, value) {
	      var data = this.__data__;
	      this.size += this.has(key) ? 0 : 1;
	      data[key] = value === undefined ? HASH_UNDEFINED : value;
	      return this;
	  };

	var SetCache = function SetCache(values) {
	      var index = -1;
	      var length = values == null ? 0 : values.length;

	      this.__data__ = new MapCache;
	      while (++index < length) {
	          this.add(values[index]);
	      }
	  };

	  SetCache.prototype.add = function add (value) {
	      this.__data__.set(value, HASH_UNDEFINED);
	      return this;
	  };

	  SetCache.prototype.has = function has (value) {
	      return this.__data__.has(value);
	  };

	SetCache.prototype.push = SetCache.prototype.add;

	// -- top level functions

	var isBoolean = function(value) {
	    var toString = Object.prototype.toString;
	    return value === true || value === false || (!!value && typeof value === 'object' && toString.call(value) === boolTag);
	};

	var isObject$1 = function(value) {
	    return !!value && (typeof value === 'object' || typeof value === 'function');
	};

	var isNumber = function(value) {
	    var toString = Object.prototype.toString;
	    return typeof value === 'number' || (!!value && typeof value === 'object' && toString.call(value) === numberTag);
	};

	var isString = function(value) {
	    var toString = Object.prototype.toString;
	    return typeof value === 'string' || (!!value && typeof value === 'object' && toString.call(value) === stringTag);
	};

	var assign = createAssigner(function (object, source) {
	    if (isPrototype(source) || isArrayLike(source)) {
	        copyObject(source, keys$1(source), object);
	        return;
	    }
	    for (var key in source) {
	        if (hasOwnProperty.call(source, key)) {
	            assignValue(object, key, source[key]);
	        }
	    }
	});

	var mixin = assign;

	var deepMixin = mixin;

	var supplement = function (object) {
	    var sources = [], len = arguments.length - 1;
	    while ( len-- > 0 ) sources[ len ] = arguments[ len + 1 ];

	    var index = -1;
	    var length = sources.length;
	    var guard = length > 2 ? sources[2] : undefined;

	    if (guard && isIterateeCall(sources[0], sources[1], guard)) {
	        length = 1;
	    }

	    while (++index < length) {
	        var source = sources[index];

	        if (source == null) {
	            continue;
	        }

	        var props = Object.keys(source);
	        var propsLength = props.length;
	        var propsIndex = -1;

	        while (++propsIndex < propsLength) {
	            var key = props[propsIndex];
	            var value = object[key];

	            if (value === undefined ||
	                (eq(value, Object.prototype[key]) && !hasOwnProperty.call(object, key))) {
	                object[key] = source[key];
	            }
	        }
	    }

	    return object;
	};

	var defaults = supplement;

	var deepSupplement = function defaultsDeep() {
	    var args = [], len = arguments.length;
	    while ( len-- ) args[ len ] = arguments[ len ];

	    args.push(undefined, customDefaultsMerge);
	    return merge.apply(undefined, args);
	};

	var defaultsDeep = deepSupplement;

	// _.invokeMap
	var invoke = function (collection, path) {
	    var args = [], len = arguments.length - 2;
	    while ( len-- > 0 ) args[ len ] = arguments[ len + 2 ];

	    var index = -1;
	    var isFunc = typeof path === 'function';
	    var result = isArrayLike(collection) ? new Array(collection.length) : [];

	    baseEach(collection, function (value) {
	        result[++index] = isFunc ? path.apply(value, args) : invokeProperty.apply(void 0, [ value, path ].concat( args ));
	    });

	    return result;
	};

	// _.invoke
	var invokeProperty = function (object, path) {
	    var args = [], len = arguments.length - 2;
	    while ( len-- > 0 ) args[ len ] = arguments[ len + 2 ];

	    path = castPath(path, object);
	    object = parent(object, path);
	    var func = object == null ? object : object[toKey(last(path))];
	    return func == null ? undefined : func.apply(object, args);
	};

	var sortedIndex = function (array, value, iteratee) {
	    var low = 0;
	    var high = array == null ? 0 : array.length;
	    if (high == 0) {
	        return 0;
	    }

	    iteratee = getIteratee(iteratee, 2);
	    value = iteratee(value);

	    var valIsNaN = value !== value;
	    var valIsNull = value === null;
	    var valIsSymbol = isSymbol(value);
	    var valIsUndefined = value === undefined;

	    while (low < high) {
	        var setLow = (void 0);
	        var mid = Math.floor((low + high) / 2);
	        var computed = iteratee(array[mid]);
	        var othIsDefined = computed !== undefined;
	        var othIsNull = computed === null;
	        var othIsReflexive = computed === computed;
	        var othIsSymbol = isSymbol(computed);

	        if (valIsNaN) {
	            setLow = othIsReflexive;
	        } else if (valIsUndefined) {
	            setLow = othIsReflexive &&othIsDefined;
	        } else if (valIsNull) {
	            setLow = othIsReflexive && othIsDefined && !othIsNull;
	        } else if (valIsSymbol) {
	            setLow = othIsReflexive && othIsDefined && !othIsNull && !othIsSymbol;
	        } else if (othIsNull || othIsSymbol) {
	            setLow = false;
	        } else {
	            setLow = computed < value;
	        }
	        if (setLow) {
	            low = mid + 1;
	        } else {
	            high = mid;
	        }
	    }
	    return Math.min(high, MAX_ARRAY_INDEX);
	};

	var uniq = function (array, iteratee) {
	    var index = -1;
	    var includes = function (array, value) {
	        var length = array == null ? 0 : array.length;
	        return !!length && array.indexOf(value) > -1;
	    };
	    iteratee = getIteratee(iteratee, 2);
	    var isCommon = true;

	    var length = array.length;
	    var result = [];
	    var seen = result;

	    if (length >= LARGE_ARRAY_SIZE) {
	        var set = iteratee ? null : createSet(array);
	        if (set) {
	            return setToArray(set);
	        }
	        isCommon = false;
	        includes = function (cache, key) { return cache.has(key); };
	        seen = new SetCache;
	    } else {
	        seen = iteratee ? [] : result;
	    }
	    outer:
	    while (++index < length) {
	        var value = array[index];
	        var computed = iteratee ? iteratee(value) : value;

	        value = (value !== 0) ? value : 0;
	        if (isCommon && computed === computed) {
	            var seenIndex = seen.length;
	            while (seenIndex--) {
	                if (seen[seenIndex] === computed) {
	                    continue outer;
	                }
	            }
	            if (iteratee) {
	                seen.push(computed);
	            }
	            result.push(value);
	        }
	        else if (!includes(seen, computed)) {
	            if (seen !== result) {
	                seen.push(computed);
	            }
	            result.push(value);
	        }
	    }
	    return result;
	};

	var clone = function (value) { return baseClone(value); };

	var cloneDeep = function (value) { return baseClone(value, true); };

	var isEmpty = function (value) {
	    if (value == null) {
	        return true;
	    }
	    if (isArrayLike(value) &&
	        (Array.isArray(value) || typeof value === 'string' || typeof value.splice === 'function' ||
	            isTypedArray(value) || isArguments(value))) {
	        return !value.length;
	    }
	    var tag = getTag(value);
	    if (tag == '[object Map]' || tag == '[object Set]') {
	        return !value.size;
	    }
	    if (isPrototype(value)) {
	        return !baseKeys(value).length;
	    }
	    for (var key in value) {
	        if (hasOwnProperty.call(value, key)) {
	            return false;
	        }
	    }
	    return true;
	};
	var isEqual = function (object, other) { return baseIsEqual(object, other); };

	var isFunction = function (value) { return typeof value === 'function'; };

	var isPlainObject = function (value) {
	    if (!isObjectLike(value) || getTag(value) != '[object Object]') {
	        return false;
	    }
	    if (Object.getPrototypeOf(value) === null) {
	        return true;
	    }
	    var proto = value;
	    while (Object.getPrototypeOf(proto) !== null) {
	        proto = Object.getPrototypeOf(proto);
	    }
	    return Object.getPrototypeOf(value) === proto;
	};

	var toArray = function (value) {
	    if (!value) {
	        return [];
	    }

	    if (isArrayLike(value)) {
	        return isString(value) ? stringToArray(value) : copyArray(value);
	    }

	    if (Symbol.iterator && Symbol.iterator in Object(value)) {
	        var iterator = value[Symbol.iterator]();
	        var data;
	        var result = [];

	        while (!(data = iterator.next()).done) {
	            result.push(data.value);
	        }
	        return result;
	    }

	    var tag = getTag(value);
	    var func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);

	    return func(value);
	};

	function debounce(func, wait, opt) {
	    if (typeof func !== 'function') {
	        throw new TypeError('Expected a function');
	    }

	    var lastArgs;
	    var lastThis;
	    var maxWait;
	    var result;
	    var timerId;
	    var lastCallTime;
	    var lastInvokeTime = 0;
	    var leading = false;
	    var maxing = false;
	    var trailing = true;

	    var useRaf = (!wait && wait !== 0 && window && typeof window.requestAnimationFrame === 'function');

	    wait = +wait || 0;

	    if (isObject$1(opt)) {
	        leading = !!opt.leading;
	        maxing = 'maxWait' in opt;
	        maxWait = maxing ? Math.max(+opt.maxWait || 0, wait) : maxWait;
	        trailing = 'trailing' in opt ? !!opt.trailing : trailing;
	    }

	    function invokeFunc(time) {
	        var args = lastArgs;
	        var thisArg = lastThis;

	        lastArgs = lastThis = undefined;
	        lastInvokeTime = time;
	        result = func.apply(thisArg, args);
	        return result;
	    }

	    function startTimer(pendingFunc, wait) {
	        if (useRaf) {
	            window.cancelAnimationFrame(timerId);
	            return window.requestAnimationFrame(pendingFunc);
	        }
	        return setTimeout(pendingFunc, wait);
	    }

	    function cancelTimer(id) {
	        if (useRaf) {
	            return window.cancelAnimationFrame(id);
	        }
	        clearTimeout(id);
	    }

	    function leadingEdge(time) {
	        lastInvokeTime = time;
	        timerId = startTimer(timerExpired, wait);
	        return leading ? invokeFunc(time) : result;
	    }

	    function remainingWait(time) {
	        var timeSinceLastCall = time - lastCallTime;
	        var timeSinceLastInvoke = time - lastInvokeTime;
	        var timeWaiting = wait - timeSinceLastCall;

	        return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
	    }

	    function shouldInvoke(time) {
	        var timeSinceLastCall = time - lastCallTime;
	        var timeSinceLastInvoke = time - lastInvokeTime;

	        return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) ||
	            (maxing && timeSinceLastInvoke >= maxWait));
	    }

	    function timerExpired() {
	        var time = Date.now();
	        if (shouldInvoke(time)) {
	            return trailingEdge(time);
	        }
	        timerId = startTimer(timerExpired, remainingWait(time));
	    }

	    function trailingEdge(time) {
	        timerId = undefined;

	        if (trailing && lastArgs) {
	            return invokeFunc(time);
	        }
	        lastArgs = lastThis = undefined;
	        return result;
	    }

	    function debounced() {
	        var args = [], len = arguments.length;
	        while ( len-- ) args[ len ] = arguments[ len ];

	        var time = Date.now();
	        var isInvoking = shouldInvoke(time);

	        lastArgs = args;
	        lastThis = this;
	        lastCallTime = time;

	        if (isInvoking) {
	            if (timerId === undefined) {
	                return leadingEdge(lastCallTime);
	            }
	            if (maxing) {
	                timerId = startTimer(timerExpired, wait);
	                return invokeFunc(lastCallTime);
	            }
	        }
	        if (timerId === undefined) {
	            timerId = startTimer(timerExpired, wait);
	        }
	        return result;
	    }

	    debounced.cancel = function () {
	        if (timerId !== undefined) {
	            cancelTimer(timerId);
	        }
	        lastInvokeTime = 0;
	        lastArgs = lastCallTime = lastThis = timerId = undefined;
	    };
	    debounced.flush = function () { return timerId === undefined ? result : trailingEdge(Date.now()); };
	    debounced.pending = function () { return timerId !== undefined; };

	    return debounced;
	}

	var groupBy = function (collection, iteratee) {
	    iteratee = getIteratee(iteratee, 2);

	    return reduce(collection, function (result, value, key) {
	        key = iteratee(value);
	        if (hasOwnProperty.call(result, key)) {
	            result[key].push(value);
	        } else {
	            assignValue(result, key, [value]);
	        }
	        return result;
	    }, {});
	};

	var sortBy = function (collection, iteratees) {
	    if ( iteratees === void 0 ) iteratees = [];

	    if (collection == null) {
	        return [];
	    }

	    var length = iteratees.length;
	    if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
	        iteratees = [];
	    } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
	        iteratees = [iteratees[0]];
	    }

	    if (!Array.isArray(iteratees)) {
	        iteratees = [getIteratee(iteratees, 2)];
	    }

	    return baseOrderBy(collection, iteratees.flat(1), []);
	};

	var flattenDeep = function (array) {
	    var length = array == null ? 0 : array.length;
	    return length ? baseFlatten(array, Infinity) : [];
	};

	var without = function (array) {
	  var values = [], len = arguments.length - 1;
	  while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];

	  return isArrayLike(array) ? diff(array, values) : [];
	};

	var difference = function (array) {
	      var values = [], len = arguments.length - 1;
	      while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];

	      return isObjectLike(array) && isArrayLike(array) ?
	        diff(array, values.flat(1)) : [];
	};

	var intersection$1 = function () {
	    var arrays = [], len = arguments.length;
	    while ( len-- ) arrays[ len ] = arguments[ len ];

	    var mapped = arrays.map(function (array) { return isObjectLike(array) && isArrayLike(array) ?
	            array : []; }
	    );

	    return mapped.length && mapped[0] === arrays[0] ?
	        intersect(mapped) : [];
	};

	var union = function () {
	    var arrays = [], len = arguments.length;
	    while ( len-- ) arrays[ len ] = arguments[ len ];

	    var array = arrays.flat(1);
	    return uniq(array);
	};

	var has$2 = function (object, key) {
	    if (object == null) {
	        return false;
	    }

	    if (typeof key === 'string') {
	        key = key.split('.');
	    }

	    var index = -1;
	    var value = object;

	    while (++index < key.length) {
	        if (!value || !hasOwnProperty.call(value, key[index])) {
	            return false;
	        }
	        value = value[key[index]];
	    }

	    return true;
	};

	var result = function (object, path, defaultValue) {
	    path = castPath(path, object);

	    var index = -1;
	    var length = path.length;

	    if (!length) {
	        length = 1;
	        object = undefined;
	    }
	    while (++index < length) {
	        var value = object == null ? undefined : object[toKey(path[index])];
	        if (value === undefined) {
	            index = length;
	            value = defaultValue;
	        }
	        object = typeof value === 'function' ? value.call(object) : value;
	    }
	    return object;
	};

	var omit = function (object) {
	    var paths = [], len = arguments.length - 1;
	    while ( len-- > 0 ) paths[ len ] = arguments[ len + 1 ];

	    var result = {};
	    if (object == null) {
	        return result;
	    }
	    var isDeep = false;
	    paths = paths.flat(1).map(function (path) {
	        path = castPath(path, object);
	        isDeep || (isDeep = path.length > 1);
	        return path;
	    });
	    copyObject(object, getAllKeysIn(object), result);
	    if (isDeep) {
	        result = baseClone(result, true, true, true, function (value) { return isPlainObject(value) ? undefined : value; });
	    }
	    var length = paths.length;
	    while (length--) {
	        unset(result, paths[length]);
	    }
	    return result;
	};

	var pick = function (object) {
	    var paths = [], len = arguments.length - 1;
	    while ( len-- > 0 ) paths[ len ] = arguments[ len + 1 ];

	    return object == null ? {} : basePick(object, paths.flat(Infinity));
	};

	var bindAll = function (object) {
	    var methodNames = [], len = arguments.length - 1;
	    while ( len-- > 0 ) methodNames[ len ] = arguments[ len + 1 ];

	    methodNames.flat(1).forEach(function (key) {
	        key = toKey(key);
	        assignValue(object, key, object[key].bind(object));
	    });
	    return object;
	};

	var forIn = function (object, iteratee) {
	    if ( iteratee === void 0 ) iteratee = function (value) { return value; };

	    var index = -1;
	    var iterable = Object(object);
	    var props = isArrayLike(object) ? arrayLikeKeys(object, true) : keysIn(object);
	    var length = props.length;

	    while(length--) {
	        var key = props[++index];
	        if (iteratee(iterable[key], key, iterable) === false) {
	            break;
	        }
	    }
	};

	var camelCase = function (string) {
	  if ( string === void 0 ) string = '';

	  return (
	    words(("" + string).replace(/['\u2019]/g, ''))
	        .reduce(function (result, word, index) {
	            word = word.toLowerCase();
	            return result + (index ? upperFirst(word) : word);
	        }, '')
	);
	};

	var idCounter = 0;

	var uniqueId = function (prefix) {
	    if ( prefix === void 0 ) prefix = '';

	    var id = ++idCounter;
	    return "" + prefix + id;
	};

	var merge = createAssigner(function (object, source, srcIndex, customizer) {
	    baseMerge(object, source, srcIndex, customizer);
	}, true);

	var addClassNamePrefix = function(className) {

	    if (!className) { return className; }

	    return className.toString().split(' ').map(function(_className) {

	        if (_className.substr(0, config.classNamePrefix.length) !== config.classNamePrefix) {
	            _className = config.classNamePrefix + _className;
	        }

	        return _className;

	    }).join(' ');
	};

	var removeClassNamePrefix = function(className) {

	    if (!className) { return className; }

	    return className.toString().split(' ').map(function(_className) {

	        if (_className.substr(0, config.classNamePrefix.length) === config.classNamePrefix) {
	            _className = _className.substr(config.classNamePrefix.length);
	        }

	        return _className;

	    }).join(' ');
	};

	var parseDOMJSON = function(json, namespace) {

	    var selectors = {};
	    var groupSelectors = {};
	    var svgNamespace = V.namespace.svg;

	    var ns = namespace || svgNamespace;
	    var fragment = document.createDocumentFragment();

	    var parseNode = function(siblingsDef, parentNode, ns) {
	        for (var i = 0; i < siblingsDef.length; i++) {
	            var nodeDef = siblingsDef[i];

	            // Text node
	            if (typeof nodeDef === 'string') {
	                var textNode = document.createTextNode(nodeDef);
	                parentNode.appendChild(textNode);
	                continue;
	            }

	            // TagName
	            if (!nodeDef.hasOwnProperty('tagName')) { throw new Error('json-dom-parser: missing tagName'); }
	            var tagName = nodeDef.tagName;

	            var node = (void 0);

	            // Namespace URI
	            if (nodeDef.hasOwnProperty('namespaceURI')) { ns = nodeDef.namespaceURI; }
	            node = document.createElementNS(ns, tagName);
	            var svg = (ns === svgNamespace);

	            var wrapper = (svg) ? V : $;
	            // Attributes
	            var attributes = nodeDef.attributes;
	            if (attributes) { wrapper(node).attr(attributes); }
	            // Style
	            var style = nodeDef.style;
	            if (style) { $(node).css(style); }
	            // ClassName
	            if (nodeDef.hasOwnProperty('className')) {
	                var className = nodeDef.className;
	                if (svg) {
	                    node.className.baseVal = className;
	                } else {
	                    node.className = className;
	                }
	            }
	            // TextContent
	            if (nodeDef.hasOwnProperty('textContent')) {
	                node.textContent = nodeDef.textContent;
	            }
	            // Selector
	            if (nodeDef.hasOwnProperty('selector')) {
	                var nodeSelector = nodeDef.selector;
	                if (selectors[nodeSelector]) { throw new Error('json-dom-parser: selector must be unique'); }
	                selectors[nodeSelector] = node;
	                wrapper(node).attr('joint-selector', nodeSelector);
	            }
	            // Groups
	            if (nodeDef.hasOwnProperty('groupSelector')) {
	                var nodeGroups = nodeDef.groupSelector;
	                if (!Array.isArray(nodeGroups)) { nodeGroups = [nodeGroups]; }
	                for (var j = 0; j < nodeGroups.length; j++) {
	                    var nodeGroup = nodeGroups[j];
	                    var group = groupSelectors[nodeGroup];
	                    if (!group) { group = groupSelectors[nodeGroup] = []; }
	                    group.push(node);
	                }
	            }

	            parentNode.appendChild(node);

	            // Children
	            var childrenDef = nodeDef.children;
	            if (Array.isArray(childrenDef)) {
	                parseNode(childrenDef, node, ns);
	            }
	        }
	    };
	    parseNode(json, fragment, ns);
	    return {
	        fragment: fragment,
	        selectors: selectors,
	        groupSelectors: groupSelectors
	    };
	};

	// Return a simple hash code from a string. See http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/.
	var hashCode = function(str) {

	    var hash = 0;
	    if (str.length === 0) { return hash; }
	    for (var i = 0; i < str.length; i++) {
	        var c = str.charCodeAt(i);
	        hash = ((hash << 5) - hash) + c;
	        hash = hash & hash; // Convert to 32bit integer
	    }
	    return hash;
	};

	var getByPath = function(obj, path, delimiter) {

	    var keys = Array.isArray(path) ? path : path.split(delimiter || '/');
	    var key;
	    var i = 0;
	    var length = keys.length;
	    while (i < length) {
	        key = keys[i++];
	        if (Object(obj) === obj && key in obj) {
	            obj = obj[key];
	        } else {
	            return undefined;
	        }
	    }
	    return obj;
	};

	var isGetSafe = function(obj, key) {
	    // Prevent prototype pollution
	    // https://snyk.io/vuln/SNYK-JS-JSON8MERGEPATCH-1038399
	    if (typeof key !== 'string' && typeof key !== 'number') {
	        key = String(key);
	    }
	    if (key === 'constructor' && typeof obj[key] === 'function') {
	        return false;
	    }
	    if (key === '__proto__') {
	        return false;
	    }
	    return true;
	};

	var setByPath = function(obj, path, value, delimiter) {

	    var keys = Array.isArray(path) ? path : path.split(delimiter || '/');
	    var last = keys.length - 1;
	    var diver = obj;
	    var i = 0;

	    for (; i < last; i++) {
	        var key = keys[i];
	        if (!isGetSafe(diver, key)) { return obj; }
	        var value$1 = diver[key];
	        // diver creates an empty object if there is no nested object under such a key.
	        // This means that one can populate an empty nested object with setByPath().
	        diver = value$1 || (diver[key] = {});
	    }

	    diver[keys[last]] = value;

	    return obj;
	};

	var unsetByPath = function(obj, path, delimiter) {

	    var keys = Array.isArray(path) ? path : path.split(delimiter || '/');
	    var last = keys.length - 1;
	    var diver = obj;
	    var i = 0;

	    for (; i < last; i++) {
	        var key = keys[i];
	        if (!isGetSafe(diver, key)) { return obj; }
	        var value = diver[key];
	        if (!value) { return obj; }
	        diver = value;
	    }

	    delete diver[keys[last]];

	    return obj;
	};

	var flattenObject = function(obj, delim, stop) {

	    delim = delim || '/';
	    var ret = {};

	    for (var key in obj) {

	        if (!obj.hasOwnProperty(key)) { continue; }

	        var shouldGoDeeper = typeof obj[key] === 'object';
	        if (shouldGoDeeper && stop && stop(obj[key])) {
	            shouldGoDeeper = false;
	        }

	        if (shouldGoDeeper) {

	            var flatObject = flattenObject(obj[key], delim, stop);

	            for (var flatKey in flatObject) {
	                if (!flatObject.hasOwnProperty(flatKey)) { continue; }
	                ret[key + delim + flatKey] = flatObject[flatKey];
	            }

	        } else {

	            ret[key] = obj[key];
	        }
	    }

	    return ret;
	};

	var uuid = function() {

	    // credit: http://stackoverflow.com/posts/2117523/revisions

	    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
	        var r = (Math.random() * 16) | 0;
	        var v = (c === 'x') ? r : (r & 0x3 | 0x8);
	        return v.toString(16);
	    });
	};

	// Generates global unique id and stores it as a property of the object, if provided.
	var guid = function(obj) {

	    guid.id = guid.id || 1;

	    if (obj === undefined) {
	        return 'j_' + guid.id++;
	    }

	    obj.id = (obj.id === undefined ? 'j_' + guid.id++ : obj.id);
	    return obj.id;
	};

	var toKebabCase = function(string) {

	    return string.replace(/[A-Z]/g, '-$&').toLowerCase();
	};

	var normalizeEvent = function(evt) {

	    if (evt.normalized) { return evt; }

	    var originalEvent = evt.originalEvent;
	    var target = evt.target;

	    // If the event is a touch event, normalize it to a mouse event.
	    var touch = originalEvent && originalEvent.changedTouches && originalEvent.changedTouches[0];
	    if (touch) {
	        for (var property in touch) {
	            // copy all the properties from the first touch that are not
	            // defined on TouchEvent (clientX, clientY, pageX, pageY, screenX, screenY, identifier, ...)
	            if (evt[property] === undefined) {
	                evt[property] = touch[property];
	            }
	        }
	    }
	    // IE: evt.target could be set to SVGElementInstance for SVGUseElement
	    if (target) {
	        var useElement = target.correspondingUseElement;
	        if (useElement) { evt.target = useElement; }
	    }

	    evt.normalized = true;

	    return evt;
	};

	var normalizeWheel = function(evt) {
	    // Sane values derived empirically
	    var PIXEL_STEP  = 10;
	    var LINE_HEIGHT = 40;
	    var PAGE_HEIGHT = 800;

	    var sX = 0, sY = 0, pX = 0, pY = 0;

	    // Legacy
	    if ('detail'      in evt) { sY = evt.detail; }
	    if ('wheelDelta'  in evt) { sY = -evt.wheelDelta / 120; }
	    if ('wheelDeltaY' in evt) { sY = -evt.wheelDeltaY / 120; }
	    if ('wheelDeltaX' in evt) { sX = -evt.wheelDeltaX / 120; }

	    // side scrolling on FF with DOMMouseScroll
	    if ( 'axis' in evt && evt.axis === evt.HORIZONTAL_AXIS ) {
	        sX = sY;
	        sY = 0;
	    }

	    pX = 'deltaX' in evt ? evt.deltaX : sX * PIXEL_STEP;
	    pY = 'deltaY' in evt ? evt.deltaY : sY * PIXEL_STEP;

	    if ((pX || pY) && evt.deltaMode) {
	        if (evt.deltaMode == 1) {
	            pX *= LINE_HEIGHT;
	            pY *= LINE_HEIGHT;
	        } else {
	            pX *= PAGE_HEIGHT;
	            pY *= PAGE_HEIGHT;
	        }
	    }

	    // macOS switches deltaX and deltaY automatically when scrolling with shift key, so this is needed in other cases
	    if (evt.deltaX === 0 && evt.deltaY !== 0 && evt.shiftKey) {
	        pX = pY;
	        pY = 0;
	        sX = sY;
	        sY = 0;
	    }

	    // Fall-back if spin cannot be determined
	    if (pX && !sX) { sX = (pX < 1) ? -1 : 1; }
	    if (pY && !sY) { sY = (pY < 1) ? -1 : 1; }

	    return {
	        spinX  : sX,
	        spinY  : sY,
	        deltaX : pX,
	        deltaY : pY,
	    };
	};

	var cap = function(val, max) {
	    return val > max ? max : val < -max ? -max : val;
	};

	var nextFrame = (function() {

	    var raf;

	    if (typeof window !== 'undefined') {

	        raf = window.requestAnimationFrame ||
	            window.webkitRequestAnimationFrame ||
	            window.mozRequestAnimationFrame ||
	            window.oRequestAnimationFrame ||
	            window.msRequestAnimationFrame;
	    }

	    if (!raf) {

	        var lastTime = 0;

	        raf = function(callback) {

	            var currTime = new Date().getTime();
	            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
	            var id = setTimeout(function() {
	                callback(currTime + timeToCall);
	            }, timeToCall);

	            lastTime = currTime + timeToCall;

	            return id;
	        };
	    }

	    return function(callback, context) {
	        var rest = [], len = arguments.length - 2;
	        while ( len-- > 0 ) rest[ len ] = arguments[ len + 2 ];

	        return (context !== undefined)
	            ? raf(callback.bind.apply(callback, [ context ].concat( rest )))
	            : raf(callback);
	    };

	})();

	var cancelFrame = (function() {

	    var caf;
	    var client = typeof window != 'undefined';

	    if (client) {

	        caf = window.cancelAnimationFrame ||
	            window.webkitCancelAnimationFrame ||
	            window.webkitCancelRequestAnimationFrame ||
	            window.msCancelAnimationFrame ||
	            window.msCancelRequestAnimationFrame ||
	            window.oCancelAnimationFrame ||
	            window.oCancelRequestAnimationFrame ||
	            window.mozCancelAnimationFrame ||
	            window.mozCancelRequestAnimationFrame;
	    }

	    caf = caf || clearTimeout;

	    return client ? caf.bind(window) : caf;

	})();

	/**
	 * @deprecated
	 */
	var shapePerimeterConnectionPoint = function(linkView, view, magnet, reference) {

	    var bbox;
	    var spot;

	    if (!magnet) {

	        // There is no magnet, try to make the best guess what is the
	        // wrapping SVG element. This is because we want this "smart"
	        // connection points to work out of the box without the
	        // programmer to put magnet marks to any of the subelements.
	        // For example, we want the function to work on basic.Path elements
	        // without any special treatment of such elements.
	        // The code below guesses the wrapping element based on
	        // one simple assumption. The wrapping elemnet is the
	        // first child of the scalable group if such a group exists
	        // or the first child of the rotatable group if not.
	        // This makese sense because usually the wrapping element
	        // is below any other sub element in the shapes.
	        var scalable = view.$('.scalable')[0];
	        var rotatable = view.$('.rotatable')[0];

	        if (scalable && scalable.firstChild) {

	            magnet = scalable.firstChild;

	        } else if (rotatable && rotatable.firstChild) {

	            magnet = rotatable.firstChild;
	        }
	    }

	    if (magnet) {

	        spot = V(magnet).findIntersection(reference, linkView.paper.cells);
	        if (!spot) {
	            bbox = V(magnet).getBBox({ target: linkView.paper.cells });
	        }

	    } else {

	        bbox = view.model.getBBox();
	        spot = bbox.intersectionWithLineFromCenterToPoint(reference);
	    }
	    return spot || bbox.center();
	};

	var isPercentage = function(val) {

	    return isString(val) && val.slice(-1) === '%';
	};

	var parseCssNumeric = function(val, restrictUnits) {

	    function getUnit(validUnitExp) {

	        // one or more numbers, followed by
	        // any number of (
	        //  `.`, followed by
	        //  one or more numbers
	        // ), followed by
	        // `validUnitExp`, followed by
	        // end of string
	        var matches = new RegExp('(?:\\d+(?:\\.\\d+)*)(' + validUnitExp + ')$').exec(val);

	        if (!matches) { return null; }
	        return matches[1];
	    }

	    var number = parseFloat(val);

	    // if `val` cannot be parsed as a number, return `null`
	    if (Number.isNaN(number)) { return null; }

	    // else: we know `output.value`
	    var output = {};
	    output.value = number;

	    // determine the unit
	    var validUnitExp;
	    if (restrictUnits == null) {
	        // no restriction
	        // accept any unit, as well as no unit
	        validUnitExp = '[A-Za-z]*';

	    } else if (Array.isArray(restrictUnits)) {
	        // if this is an empty array, top restriction - return `null`
	        if (restrictUnits.length === 0) { return null; }

	        // else: restriction - an array of valid unit strings
	        validUnitExp = restrictUnits.join('|');

	    } else if (isString(restrictUnits)) {
	        // restriction - a single valid unit string
	        validUnitExp = restrictUnits;
	    }
	    var unit = getUnit(validUnitExp);

	    // if we found no matches for `restrictUnits`, return `null`
	    if (unit === null) { return null; }

	    // else: we know the unit
	    output.unit = unit;
	    return output;
	};

	var NO_SPACE = 0;

	function splitWordWithEOL(word, eol) {
	    var eolWords = word.split(eol);
	    var n = 1;
	    for (var j = 0, jl = eolWords.length - 1; j < jl; j++) {
	        var replacement = [];
	        if (j > 0 || eolWords[0] !== '') { replacement.push(NO_SPACE); }
	        replacement.push(eol);
	        if (j < jl - 1 || eolWords[jl] !== '') { replacement.push(NO_SPACE); }
	        eolWords.splice.apply(eolWords, [ n, 0 ].concat( replacement ));
	        n += replacement.length + 1;
	    }
	    return eolWords.filter(function (word) { return word !== ''; });
	}


	function getLineHeight(heightValue, textElement) {
	    if (heightValue === null) {
	        // Default 1em lineHeight
	        return textElement.getBBox().height;
	    }

	    switch (heightValue.unit) {
	        case 'em':
	            return textElement.getBBox().height * heightValue.value;
	        case 'px':
	        case '':
	            return heightValue.value;
	    }
	}

	var breakText = function(text, size, styles, opt) {
	    if ( styles === void 0 ) styles = {};
	    if ( opt === void 0 ) opt = {};


	    var width = size.width;
	    var height = size.height;

	    var svgDocument = opt.svgDocument || V('svg').node;
	    var textSpan = V('tspan').node;
	    var textElement = V('text').attr(styles).append(textSpan).node;
	    var textNode = document.createTextNode('');

	    // Prevent flickering
	    textElement.style.opacity = 0;
	    // Prevent FF from throwing an uncaught exception when `getBBox()`
	    // called on element that is not in the render tree (is not measurable).
	    // <tspan>.getComputedTextLength() returns always 0 in this case.
	    // Note that the `textElement` resp. `textSpan` can become hidden
	    // when it's appended to the DOM and a `display: none` CSS stylesheet
	    // rule gets applied.
	    textElement.style.display = 'block';
	    textSpan.style.display = 'block';

	    textSpan.appendChild(textNode);
	    svgDocument.appendChild(textElement); // lgtm [js/xss-through-dom]

	    if (!opt.svgDocument) {

	        document.body.appendChild(svgDocument);
	    }

	    var preserveSpaces = opt.preserveSpaces;
	    var space = ' ';
	    var separator = opt.separator || space;
	    var eol = opt.eol || '\n';
	    var hyphen = opt.hyphen ? new RegExp(opt.hyphen) : /[^\w\d]/;
	    var maxLineCount = opt.maxLineCount;
	    if (!isNumber(maxLineCount)) { maxLineCount = Infinity; }

	    var words = text.split(separator);
	    var full = [];
	    var lines = [];
	    var p, h;
	    var lineHeight;

	    if (preserveSpaces) {
	        V(textSpan).attr('xml:space', 'preserve');
	    }

	    for (var i = 0, l = 0, len = words.length; i < len; i++) {

	        var word = words[i];

	        if (!word && !preserveSpaces) { continue; }
	        if (typeof word !== 'string') { continue; }

	        var isEol = false;
	        if (eol && word.indexOf(eol) >= 0) {
	            // word contains end-of-line character
	            if (word.length > 1) {
	                // separate word and continue cycle
	                var eolWords = splitWordWithEOL(words[i], eol);
	                words.splice.apply(words, [ i, 1 ].concat( eolWords ));
	                i--;
	                len = words.length;
	                continue;
	            } else {
	                // creates a new line
	                if (preserveSpaces && typeof words[i - 1] === 'string' ) {
	                    words.splice(i, NO_SPACE, '', NO_SPACE);
	                    len += 2;
	                    i--;
	                    continue;
	                }
	                lines[++l] = (!preserveSpaces || typeof words[i + 1] === 'string') ? '' : undefined;
	                isEol = true;
	            }
	        }

	        if (!isEol) {

	            var data = (void 0);
	            if (preserveSpaces) {
	                data = lines[l] !== undefined ? lines[l] + space + word : word;
	            } else {
	                data = lines[l] ? lines[l] + space + word : word;
	            }

	            textNode.data = data;

	            if (textSpan.getComputedTextLength() <= width) {

	                // the current line fits
	                lines[l] = data;

	                if (p || h) {
	                // We were partitioning. Put rest of the word onto next line
	                    full[l++] = true;

	                    // cancel partitioning and splitting by hyphens
	                    p = 0;
	                    h = 0;
	                }

	            } else {

	                if (!lines[l] || p) {

	                    var partition = !!p;

	                    p = word.length - 1;

	                    if (partition || !p) {

	                        // word has only one character.
	                        if (!p) {

	                            if (!lines[l]) {

	                                // we won't fit this text within our rect
	                                lines = [];

	                                break;
	                            }

	                            // partitioning didn't help on the non-empty line
	                            // try again, but this time start with a new line

	                            // cancel partitions created
	                            words.splice(i, 2, word + words[i + 1]);

	                            // adjust word length
	                            len--;

	                            full[l++] = true;
	                            i--;

	                            continue;
	                        }

	                        // move last letter to the beginning of the next word
	                        words[i] = word.substring(0, p);
	                        words[i + 1] = word.substring(p) + (words[i + 1] === undefined ? '' : words[i + 1]);

	                    } else {

	                        if (h) {
	                        // cancel splitting and put the words together again
	                            words.splice(i, 2, words[i] + words[i + 1]);
	                            h = 0;
	                        } else {
	                            var hyphenIndex = word.search(hyphen);
	                            if (hyphenIndex > -1 && hyphenIndex !== word.length - 1 && hyphenIndex !== 0) {
	                                h = hyphenIndex + 1;
	                                p = 0;
	                            }

	                            // We initiate partitioning or splitting
	                            // split the long word into two words
	                            words.splice(i, 1, word.substring(0, h || p), word.substring(h|| p));
	                            // adjust words length
	                            len++;

	                        }

	                        if (l && !full[l - 1]) {
	                        // if the previous line is not full, try to fit max part of
	                        // the current word there
	                            l--;
	                        }
	                    }

	                    if (!preserveSpaces || lines[l] !== '') {
	                        i--;
	                    }

	                    continue;
	                }

	                l++;
	                i--;
	            }
	        }
	        var lastL = null;

	        if (lines.length > maxLineCount) {

	            lastL = maxLineCount - 1;

	        } else if (height !== undefined) {

	            // if size.height is defined we have to check whether the height of the entire
	            // text exceeds the rect height

	            if (lineHeight === undefined && textNode.data !== '') {

	                // use the same defaults as in V.prototype.text
	                if (styles.lineHeight === 'auto') {
	                    lineHeight = getLineHeight({ value: 1.5, unit: 'em' }, textElement);
	                } else {
	                    var parsed = parseCssNumeric(styles.lineHeight, ['em', 'px', '']);

	                    lineHeight = getLineHeight(parsed, textElement);
	                }
	            }

	            if (lineHeight * lines.length > height) {
	                // remove overflowing lines
	                lastL = Math.floor(height / lineHeight) - 1;
	            }
	        }

	        if (lastL !== null) {

	            lines.splice(lastL + 1);

	            // add ellipsis
	            var ellipsis = opt.ellipsis;
	            if (!ellipsis || lastL < 0) { break; }
	            if (typeof ellipsis !== 'string') { ellipsis = '\u2026'; }

	            var lastLine = lines[lastL];
	            if (!lastLine && !isEol) { break; }
	            var k = lastLine.length;
	            var lastLineWithOmission, lastChar, separatorChar;
	            do {
	                lastChar = lastLine[k];
	                lastLineWithOmission = lastLine.substring(0, k);
	                if (!lastChar) {
	                    separatorChar = (typeof separator === 'string') ? separator : ' ';
	                    lastLineWithOmission += separatorChar;
	                } else if (lastChar.match(separator)) {
	                    lastLineWithOmission += lastChar;
	                }
	                lastLineWithOmission += ellipsis;
	                textNode.data = lastLineWithOmission;
	                if (textSpan.getComputedTextLength() <= width) {
	                    lines[lastL] = lastLineWithOmission;
	                    break;
	                }
	                k--;
	            } while (k >= 0);
	            break;
	        }
	    }

	    if (opt.svgDocument) {

	        // svg document was provided, remove the text element only
	        svgDocument.removeChild(textElement);

	    } else {

	        // clean svg document
	        document.body.removeChild(svgDocument);
	    }

	    return lines.join(eol);
	};

	// Sanitize HTML
	// Based on https://gist.github.com/ufologist/5a0da51b2b9ef1b861c30254172ac3c9
	// Parses a string into an array of DOM nodes.
	// Then outputs it back as a string.
	var sanitizeHTML = function(html) {

	    // Ignores tags that are invalid inside a <div> tag (e.g. <body>, <head>)

	    // If documentContext (second parameter) is not specified or given as `null` or `undefined`, a new document is used.
	    // Inline events will not execute when the HTML is parsed; this includes, for example, sending GET requests for images.

	    // If keepScripts (last parameter) is `false`, scripts are not executed.
	    var output = $($.parseHTML('<div>' + html + '</div>', null, false));

	    output.find('*').each(function() { // for all nodes
	        var currentNode = this;

	        $.each(currentNode.attributes, function() { // for all attributes in each node
	            var currentAttribute = this;

	            var attrName = currentAttribute.name;
	            var attrValue = currentAttribute.value;

	            // Remove attribute names that start with "on" (e.g. onload, onerror...).
	            // Remove attribute values that start with "javascript:" pseudo protocol (e.g. `href="javascript:alert(1)"`).
	            if (attrName.startsWith('on') || attrValue.startsWith('javascript:') || attrValue.startsWith('data:') || attrValue.startsWith('vbscript:')) {
	                $(currentNode).removeAttr(attrName);
	            }
	        });
	    });

	    return output.html();
	};

	// Download `blob` as file with `fileName`.
	// Does not work in IE9.
	var downloadBlob = function(blob, fileName) {

	    if (window.navigator.msSaveBlob) { // requires IE 10+
	        // pulls up a save dialog
	        window.navigator.msSaveBlob(blob, fileName);

	    } else { // other browsers
	        // downloads directly in Chrome and Safari

	        // presents a save/open dialog in Firefox
	        // Firefox bug: `from` field in save dialog always shows `from:blob:`
	        // https://bugzilla.mozilla.org/show_bug.cgi?id=1053327

	        var url = window.URL.createObjectURL(blob);
	        var link = document.createElement('a');

	        link.href = url;
	        link.download = fileName;
	        document.body.appendChild(link);

	        link.click();

	        document.body.removeChild(link);
	        window.URL.revokeObjectURL(url); // mark the url for garbage collection
	    }
	};

	// Download `dataUri` as file with `fileName`.
	// Does not work in IE9.
	var downloadDataUri = function(dataUri, fileName) {

	    var blob = dataUriToBlob(dataUri);
	    downloadBlob(blob, fileName);
	};

	// Convert an uri-encoded data component (possibly also base64-encoded) to a blob.
	var dataUriToBlob = function(dataUri) {

	    // first, make sure there are no newlines in the data uri
	    dataUri = dataUri.replace(/\s/g, '');
	    dataUri = decodeURIComponent(dataUri);

	    var firstCommaIndex = dataUri.indexOf(','); // split dataUri as `dataTypeString`,`data`

	    var dataTypeString = dataUri.slice(0, firstCommaIndex); // e.g. 'data:image/jpeg;base64'
	    var mimeString = dataTypeString.split(':')[1].split(';')[0]; // e.g. 'image/jpeg'

	    var data = dataUri.slice(firstCommaIndex + 1);
	    var decodedString;
	    if (dataTypeString.indexOf('base64') >= 0) { // data may be encoded in base64
	        decodedString = atob(data); // decode data
	    } else {
	        // convert the decoded string to UTF-8
	        decodedString = unescape(encodeURIComponent(data));
	    }
	    // write the bytes of the string to a typed array
	    var ia = new Uint8Array(decodedString.length);
	    for (var i = 0; i < decodedString.length; i++) {
	        ia[i] = decodedString.charCodeAt(i);
	    }

	    return new Blob([ia], { type: mimeString }); // return the typed array as Blob
	};

	// Read an image at `url` and return it as base64-encoded data uri.
	// The mime type of the image is inferred from the `url` file extension.
	// If data uri is provided as `url`, it is returned back unchanged.
	// `callback` is a method with `err` as first argument and `dataUri` as second argument.
	// Works with IE9.
	var imageToDataUri = function(url, callback) {

	    if (!url || url.substr(0, 'data:'.length) === 'data:') {
	        // No need to convert to data uri if it is already in data uri.

	        // This not only convenient but desired. For example,
	        // IE throws a security error if data:image/svg+xml is used to render
	        // an image to the canvas and an attempt is made to read out data uri.
	        // Now if our image is already in data uri, there is no need to render it to the canvas
	        // and so we can bypass this error.

	        // Keep the async nature of the function.
	        return setTimeout(function() {
	            callback(null, url);
	        }, 0);
	    }

	    // chrome, IE10+
	    var modernHandler = function(xhr, callback) {

	        if (xhr.status === 200) {

	            var reader = new FileReader();

	            reader.onload = function(evt) {
	                var dataUri = evt.target.result;
	                callback(null, dataUri);
	            };

	            reader.onerror = function() {
	                callback(new Error('Failed to load image ' + url));
	            };

	            reader.readAsDataURL(xhr.response);
	        } else {
	            callback(new Error('Failed to load image ' + url));
	        }
	    };

	    var legacyHandler = function(xhr, callback) {

	        var Uint8ToString = function(u8a) {
	            var CHUNK_SZ = 0x8000;
	            var c = [];
	            for (var i = 0; i < u8a.length; i += CHUNK_SZ) {
	                c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
	            }
	            return c.join('');
	        };

	        if (xhr.status === 200) {

	            var bytes = new Uint8Array(xhr.response);

	            var suffix = (url.split('.').pop()) || 'png';
	            var map = {
	                'svg': 'svg+xml'
	            };
	            var meta = 'data:image/' + (map[suffix] || suffix) + ';base64,';
	            var b64encoded = meta + btoa(Uint8ToString(bytes));
	            callback(null, b64encoded);
	        } else {
	            callback(new Error('Failed to load image ' + url));
	        }
	    };

	    var xhr = new XMLHttpRequest();

	    xhr.open('GET', url, true);
	    xhr.addEventListener('error', function() {
	        callback(new Error('Failed to load image ' + url));
	    });

	    xhr.responseType = window.FileReader ? 'blob' : 'arraybuffer';

	    xhr.addEventListener('load', function() {
	        if (window.FileReader) {
	            modernHandler(xhr, callback);
	        } else {
	            legacyHandler(xhr, callback);
	        }
	    });

	    xhr.send();
	};

	var getElementBBox = function(el) {

	    var $el = $(el);
	    if ($el.length === 0) {
	        throw new Error('Element not found');
	    }

	    var element = $el[0];
	    var doc = element.ownerDocument;
	    var clientBBox = element.getBoundingClientRect();

	    var strokeWidthX = 0;
	    var strokeWidthY = 0;

	    // Firefox correction
	    if (element.ownerSVGElement) {

	        var vel = V(element);
	        var bbox = vel.getBBox({ target: vel.svg() });

	        // if FF getBoundingClientRect includes stroke-width, getBBox doesn't.
	        // To unify this across all browsers we need to adjust the final bBox with `stroke-width` value.
	        strokeWidthX = (clientBBox.width - bbox.width);
	        strokeWidthY = (clientBBox.height - bbox.height);
	    }

	    return {
	        x: clientBBox.left + window.pageXOffset - doc.documentElement.offsetLeft + strokeWidthX / 2,
	        y: clientBBox.top + window.pageYOffset - doc.documentElement.offsetTop + strokeWidthY / 2,
	        width: clientBBox.width - strokeWidthX,
	        height: clientBBox.height - strokeWidthY
	    };
	};


	// Highly inspired by the jquery.sortElements plugin by Padolsey.
	// See http://james.padolsey.com/javascript/sorting-elements-with-jquery/.
	var sortElements = function(elements, comparator) {

	    var $elements = $(elements);
	    var placements = $elements.map(function() {

	        var sortElement = this;
	        var parentNode = sortElement.parentNode;
	        // Since the element itself will change position, we have
	        // to have some way of storing it's original position in
	        // the DOM. The easiest way is to have a 'flag' node:
	        var nextSibling = parentNode.insertBefore(document.createTextNode(''), sortElement.nextSibling);

	        return function() {

	            if (parentNode === this) {
	                throw new Error('You can\'t sort elements if any one is a descendant of another.');
	            }

	            // Insert before flag:
	            parentNode.insertBefore(this, nextSibling);
	            // Remove flag:
	            parentNode.removeChild(nextSibling);
	        };
	    });

	    return Array.prototype.sort.call($elements, comparator).each(function(i) {
	        placements[i].call(this);
	    });
	};

	// Sets attributes on the given element and its descendants based on the selector.
	// `attrs` object: { [SELECTOR1]: { attrs1 }, [SELECTOR2]: { attrs2}, ... } e.g. { 'input': { color : 'red' }}
	var setAttributesBySelector = function(element, attrs) {

	    var $element = $(element);

	    forIn(attrs, function(attrs, selector) {
	        var $elements = $element.find(selector).addBack().filter(selector);
	        // Make a special case for setting classes.
	        // We do not want to overwrite any existing class.
	        if (has$2(attrs, 'class')) {
	            $elements.addClass(attrs['class']);
	            attrs = omit(attrs, 'class');
	        }
	        $elements.attr(attrs);
	    });
	};

	// Return a new object with all four sides (top, right, bottom, left) in it.
	// Value of each side is taken from the given argument (either number or object).
	// Default value for a side is 0.
	// Examples:
	// normalizeSides(5) --> { top: 5, right: 5, bottom: 5, left: 5 }
	// normalizeSides({ horizontal: 5 }) --> { top: 0, right: 5, bottom: 0, left: 5 }
	// normalizeSides({ left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 }
	// normalizeSides({ horizontal: 10, left: 5 }) --> { top: 0, right: 10, bottom: 0, left: 5 }
	// normalizeSides({ horizontal: 0, left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 }
	var normalizeSides = function(box) {

	    if (Object(box) !== box) { // `box` is not an object
	        var val = 0; // `val` left as 0 if `box` cannot be understood as finite number
	        if (isFinite(box)) { val = +box; } // actually also accepts string numbers (e.g. '100')

	        return { top: val, right: val, bottom: val, left: val };
	    }

	    // `box` is an object
	    var top, right, bottom, left;
	    top = right = bottom = left = 0;

	    if (isFinite(box.vertical)) { top = bottom = +box.vertical; }
	    if (isFinite(box.horizontal)) { right = left = +box.horizontal; }

	    if (isFinite(box.top)) { top = +box.top; } // overwrite vertical
	    if (isFinite(box.right)) { right = +box.right; } // overwrite horizontal
	    if (isFinite(box.bottom)) { bottom = +box.bottom; } // overwrite vertical
	    if (isFinite(box.left)) { left = +box.left; } // overwrite horizontal

	    return { top: top, right: right, bottom: bottom, left: left };
	};

	var timing = {

	    linear: function(t) {
	        return t;
	    },

	    quad: function(t) {
	        return t * t;
	    },

	    cubic: function(t) {
	        return t * t * t;
	    },

	    inout: function(t) {
	        if (t <= 0) { return 0; }
	        if (t >= 1) { return 1; }
	        var t2 = t * t;
	        var t3 = t2 * t;
	        return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
	    },

	    exponential: function(t) {
	        return Math.pow(2, 10 * (t - 1));
	    },

	    bounce: function(t) {
	        for (var a = 0, b = 1; 1; a += b, b /= 2) {
	            if (t >= (7 - 4 * a) / 11) {
	                var q = (11 - 6 * a - 11 * t) / 4;
	                return -q * q + b * b;
	            }
	        }
	    },

	    reverse: function(f) {
	        return function(t) {
	            return 1 - f(1 - t);
	        };
	    },

	    reflect: function(f) {
	        return function(t) {
	            return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t)));
	        };
	    },

	    clamp: function(f, n, x) {
	        n = n || 0;
	        x = x || 1;
	        return function(t) {
	            var r = f(t);
	            return r < n ? n : r > x ? x : r;
	        };
	    },

	    back: function(s) {
	        if (!s) { s = 1.70158; }
	        return function(t) {
	            return t * t * ((s + 1) * t - s);
	        };
	    },

	    elastic: function(x) {
	        if (!x) { x = 1.5; }
	        return function(t) {
	            return Math.pow(2, 10 * (t - 1)) * Math.cos(20 * Math.PI * x / 3 * t);
	        };
	    }
	};

	var interpolate = {

	    number: function(a, b) {
	        var d = b - a;
	        return function(t) {
	            return a + d * t;
	        };
	    },

	    object: function(a, b) {
	        var s = Object.keys(a);
	        return function(t) {
	            var i, p;
	            var r = {};
	            for (i = s.length - 1; i != -1; i--) {
	                p = s[i];
	                r[p] = a[p] + (b[p] - a[p]) * t;
	            }
	            return r;
	        };
	    },

	    hexColor: function(a, b) {

	        var ca = parseInt(a.slice(1), 16);
	        var cb = parseInt(b.slice(1), 16);
	        var ra = ca & 0x0000ff;
	        var rd = (cb & 0x0000ff) - ra;
	        var ga = ca & 0x00ff00;
	        var gd = (cb & 0x00ff00) - ga;
	        var ba = ca & 0xff0000;
	        var bd = (cb & 0xff0000) - ba;

	        return function(t) {

	            var r = (ra + rd * t) & 0x000000ff;
	            var g = (ga + gd * t) & 0x0000ff00;
	            var b = (ba + bd * t) & 0x00ff0000;

	            return '#' + (1 << 24 | r | g | b).toString(16).slice(1);
	        };
	    },

	    unit: function(a, b) {

	        var r = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/;
	        var ma = r.exec(a);
	        var mb = r.exec(b);
	        var p = mb[1].indexOf('.');
	        var f = p > 0 ? mb[1].length - p - 1 : 0;
	        a = +ma[1];
	        var d = +mb[1] - a;
	        var u = ma[2];

	        return function(t) {
	            return (a + d * t).toFixed(f) + u;
	        };
	    }
	};

	// SVG filters.
	// (values in parentheses are default values)
	var filter = {

	    // `color` ... outline color ('blue')
	    // `width`... outline width (1)
	    // `opacity` ... outline opacity (1)
	    // `margin` ... gap between outline and the element (2)
	    outline: function(args) {

	        var tpl = '<filter><feFlood flood-color="${color}" flood-opacity="${opacity}" result="colored"/><feMorphology in="SourceAlpha" result="morphedOuter" operator="dilate" radius="${outerRadius}" /><feMorphology in="SourceAlpha" result="morphedInner" operator="dilate" radius="${innerRadius}" /><feComposite result="morphedOuterColored" in="colored" in2="morphedOuter" operator="in"/><feComposite operator="xor" in="morphedOuterColored" in2="morphedInner" result="outline"/><feMerge><feMergeNode in="outline"/><feMergeNode in="SourceGraphic"/></feMerge></filter>';

	        var margin = Number.isFinite(args.margin) ? args.margin : 2;
	        var width = Number.isFinite(args.width) ? args.width : 1;

	        return template(tpl)({
	            color: args.color || 'blue',
	            opacity: Number.isFinite(args.opacity) ? args.opacity : 1,
	            outerRadius: margin + width,
	            innerRadius: margin
	        });
	    },

	    // `color` ... color ('red')
	    // `width`... width (1)
	    // `blur` ... blur (0)
	    // `opacity` ... opacity (1)
	    highlight: function(args) {

	        var tpl = '<filter><feFlood flood-color="${color}" flood-opacity="${opacity}" result="colored"/><feMorphology result="morphed" in="SourceGraphic" operator="dilate" radius="${width}"/><feComposite result="composed" in="colored" in2="morphed" operator="in"/><feGaussianBlur result="blured" in="composed" stdDeviation="${blur}"/><feBlend in="SourceGraphic" in2="blured" mode="normal"/></filter>';

	        return template(tpl)({
	            color: args.color || 'red',
	            width: Number.isFinite(args.width) ? args.width : 1,
	            blur: Number.isFinite(args.blur) ? args.blur : 0,
	            opacity: Number.isFinite(args.opacity) ? args.opacity : 1
	        });
	    },

	    // `x` ... horizontal blur (2)
	    // `y` ... vertical blur (optional)
	    blur: function(args) {

	        var x = Number.isFinite(args.x) ? args.x : 2;

	        return template('<filter><feGaussianBlur stdDeviation="${stdDeviation}"/></filter>')({
	            stdDeviation: Number.isFinite(args.y) ? [x, args.y] : x
	        });
	    },

	    // `dx` ... horizontal shift (0)
	    // `dy` ... vertical shift (0)
	    // `blur` ... blur (4)
	    // `color` ... color ('black')
	    // `opacity` ... opacity (1)
	    dropShadow: function(args) {

	        var tpl = 'SVGFEDropShadowElement' in window
	            ? '<filter><feDropShadow stdDeviation="${blur}" dx="${dx}" dy="${dy}" flood-color="${color}" flood-opacity="${opacity}"/></filter>'
	            : '<filter><feGaussianBlur in="SourceAlpha" stdDeviation="${blur}"/><feOffset dx="${dx}" dy="${dy}" result="offsetblur"/><feFlood flood-color="${color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="${opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter>';

	        return template(tpl)({
	            dx: args.dx || 0,
	            dy: args.dy || 0,
	            opacity: Number.isFinite(args.opacity) ? args.opacity : 1,
	            color: args.color || 'black',
	            blur: Number.isFinite(args.blur) ? args.blur : 4
	        });
	    },

	    // `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely grayscale. A value of 0 leaves the input unchanged.
	    grayscale: function(args) {

	        var amount = Number.isFinite(args.amount) ? args.amount : 1;

	        return template('<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 0 ${d} ${e} ${f} 0 0 ${g} ${b} ${h} 0 0 0 0 0 1 0"/></filter>')({
	            a: 0.2126 + 0.7874 * (1 - amount),
	            b: 0.7152 - 0.7152 * (1 - amount),
	            c: 0.0722 - 0.0722 * (1 - amount),
	            d: 0.2126 - 0.2126 * (1 - amount),
	            e: 0.7152 + 0.2848 * (1 - amount),
	            f: 0.0722 - 0.0722 * (1 - amount),
	            g: 0.2126 - 0.2126 * (1 - amount),
	            h: 0.0722 + 0.9278 * (1 - amount)
	        });
	    },

	    // `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely sepia. A value of 0 leaves the input unchanged.
	    sepia: function(args) {

	        var amount = Number.isFinite(args.amount) ? args.amount : 1;

	        return template('<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 0 ${d} ${e} ${f} 0 0 ${g} ${h} ${i} 0 0 0 0 0 1 0"/></filter>')({
	            a: 0.393 + 0.607 * (1 - amount),
	            b: 0.769 - 0.769 * (1 - amount),
	            c: 0.189 - 0.189 * (1 - amount),
	            d: 0.349 - 0.349 * (1 - amount),
	            e: 0.686 + 0.314 * (1 - amount),
	            f: 0.168 - 0.168 * (1 - amount),
	            g: 0.272 - 0.272 * (1 - amount),
	            h: 0.534 - 0.534 * (1 - amount),
	            i: 0.131 + 0.869 * (1 - amount)
	        });
	    },

	    // `amount` ... the proportion of the conversion (1). A value of 0 is completely un-saturated. A value of 1 (default) leaves the input unchanged.
	    saturate: function(args) {

	        var amount = Number.isFinite(args.amount) ? args.amount : 1;

	        return template('<filter><feColorMatrix type="saturate" values="${amount}"/></filter>')({
	            amount: 1 - amount
	        });
	    },

	    // `angle` ...  the number of degrees around the color circle the input samples will be adjusted (0).
	    hueRotate: function(args) {

	        return template('<filter><feColorMatrix type="hueRotate" values="${angle}"/></filter>')({
	            angle: args.angle || 0
	        });
	    },

	    // `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely inverted. A value of 0 leaves the input unchanged.
	    invert: function(args) {

	        var amount = Number.isFinite(args.amount) ? args.amount : 1;

	        return template('<filter><feComponentTransfer><feFuncR type="table" tableValues="${amount} ${amount2}"/><feFuncG type="table" tableValues="${amount} ${amount2}"/><feFuncB type="table" tableValues="${amount} ${amount2}"/></feComponentTransfer></filter>')({
	            amount: amount,
	            amount2: 1 - amount
	        });
	    },

	    // `amount` ... proportion of the conversion (1). A value of 0 will create an image that is completely black. A value of 1 (default) leaves the input unchanged.
	    brightness: function(args) {

	        return template('<filter><feComponentTransfer><feFuncR type="linear" slope="${amount}"/><feFuncG type="linear" slope="${amount}"/><feFuncB type="linear" slope="${amount}"/></feComponentTransfer></filter>')({
	            amount: Number.isFinite(args.amount) ? args.amount : 1
	        });
	    },

	    // `amount` ... proportion of the conversion (1). A value of 0 will create an image that is completely black. A value of 1 (default) leaves the input unchanged.
	    contrast: function(args) {

	        var amount = Number.isFinite(args.amount) ? args.amount : 1;

	        return template('<filter><feComponentTransfer><feFuncR type="linear" slope="${amount}" intercept="${amount2}"/><feFuncG type="linear" slope="${amount}" intercept="${amount2}"/><feFuncB type="linear" slope="${amount}" intercept="${amount2}"/></feComponentTransfer></filter>')({
	            amount: amount,
	            amount2: .5 - amount / 2
	        });
	    }
	};

	var format = {

	    // Formatting numbers via the Python Format Specification Mini-language.
	    // See http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language.
	    // Heavilly inspired by the D3.js library implementation.
	    number: function(specifier, value, locale) {

	        locale = locale || {

	            currency: ['$', ''],
	            decimal: '.',
	            thousands: ',',
	            grouping: [3]
	        };

	        // See Python format specification mini-language: http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language.
	        // [[fill]align][sign][symbol][0][width][,][.precision][type]
	        var re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;

	        var match = re.exec(specifier);
	        var fill = match[1] || ' ';
	        var align = match[2] || '>';
	        var sign = match[3] || '';
	        var symbol = match[4] || '';
	        var zfill = match[5];
	        var width = +match[6];
	        var comma = match[7];
	        var precision = match[8];
	        var type = match[9];
	        var scale = 1;
	        var prefix = '';
	        var suffix = '';
	        var integer = false;

	        if (precision) { precision = +precision.substring(1); }

	        if (zfill || fill === '0' && align === '=') {
	            zfill = fill = '0';
	            align = '=';
	            if (comma) { width -= Math.floor((width - 1) / 4); }
	        }

	        switch (type) {
	            case 'n':
	                comma = true;
	                type = 'g';
	                break;
	            case '%':
	                scale = 100;
	                suffix = '%';
	                type = 'f';
	                break;
	            case 'p':
	                scale = 100;
	                suffix = '%';
	                type = 'r';
	                break;
	            case 'b':
	            case 'o':
	            case 'x':
	            case 'X':
	                if (symbol === '#') { prefix = '0' + type.toLowerCase(); }
	                break;
	            case 'c':
	            case 'd':
	                integer = true;
	                precision = 0;
	                break;
	            case 's':
	                scale = -1;
	                type = 'r';
	                break;
	        }

	        if (symbol === '$') {
	            prefix = locale.currency[0];
	            suffix = locale.currency[1];
	        }

	        // If no precision is specified for `'r'`, fallback to general notation.
	        if (type == 'r' && !precision) { type = 'g'; }

	        // Ensure that the requested precision is in the supported range.
	        if (precision != null) {
	            if (type == 'g') { precision = Math.max(1, Math.min(21, precision)); }
	            else if (type == 'e' || type == 'f') { precision = Math.max(0, Math.min(20, precision)); }
	        }

	        var zcomma = zfill && comma;

	        // Return the empty string for floats formatted as ints.
	        if (integer && (value % 1)) { return ''; }

	        // Convert negative to positive, and record the sign prefix.
	        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, '-') : sign;

	        var fullSuffix = suffix;

	        // Apply the scale, computing it from the value's exponent for si format.
	        // Preserve the existing suffix, if any, such as the currency symbol.
	        if (scale < 0) {
	            var unit = this.prefix(value, precision);
	            value = unit.scale(value);
	            fullSuffix = unit.symbol + suffix;
	        } else {
	            value *= scale;
	        }

	        // Convert to the desired precision.
	        value = this.convert(type, value, precision);

	        // Break the value into the integer part (before) and decimal part (after).
	        var i = value.lastIndexOf('.');
	        var before = i < 0 ? value : value.substring(0, i);
	        var after = i < 0 ? '' : locale.decimal + value.substring(i + 1);

	        function formatGroup(value) {

	            var i = value.length;
	            var t = [];
	            var j = 0;
	            var g = locale.grouping[0];
	            while (i > 0 && g > 0) {
	                t.push(value.substring(i -= g, i + g));
	                g = locale.grouping[j = (j + 1) % locale.grouping.length];
	            }
	            return t.reverse().join(locale.thousands);
	        }

	        // If the fill character is not `'0'`, grouping is applied before padding.
	        if (!zfill && comma && locale.grouping) {

	            before = formatGroup(before);
	        }

	        var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length);
	        var padding = length < width ? new Array(length = width - length + 1).join(fill) : '';

	        // If the fill character is `'0'`, grouping is applied after padding.
	        if (zcomma) { before = formatGroup(padding + before); }

	        // Apply prefix.
	        negative += prefix;

	        // Rejoin integer and decimal parts.
	        value = before + after;

	        return (align === '<' ? negative + value + padding
	            : align === '>' ? padding + negative + value
	                : align === '^' ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length)
	                    : negative + (zcomma ? value : padding + value)) + fullSuffix;
	    },

	    // Formatting string via the Python Format string.
	    // See https://docs.python.org/2/library/string.html#format-string-syntax)
	    string: function(formatString, value) {

	        var fieldDelimiterIndex;
	        var fieldDelimiter = '{';
	        var endPlaceholder = false;
	        var formattedStringArray = [];

	        while ((fieldDelimiterIndex = formatString.indexOf(fieldDelimiter)) !== -1) {

	            var pieceFormattedString, formatSpec, fieldName;

	            pieceFormattedString = formatString.slice(0, fieldDelimiterIndex);

	            if (endPlaceholder) {
	                formatSpec = pieceFormattedString.split(':');
	                fieldName = formatSpec.shift().split('.');
	                pieceFormattedString = value;

	                for (var i = 0; i < fieldName.length; i++)
	                    { pieceFormattedString = pieceFormattedString[fieldName[i]]; }

	                if (formatSpec.length)
	                    { pieceFormattedString = this.number(formatSpec, pieceFormattedString); }
	            }

	            formattedStringArray.push(pieceFormattedString);

	            formatString = formatString.slice(fieldDelimiterIndex + 1);
	            endPlaceholder = !endPlaceholder;
	            fieldDelimiter = (endPlaceholder) ? '}' : '{';
	        }
	        formattedStringArray.push(formatString);

	        return formattedStringArray.join('');
	    },

	    convert: function(type, value, precision) {

	        switch (type) {
	            case 'b':
	                return value.toString(2);
	            case 'c':
	                return String.fromCharCode(value);
	            case 'o':
	                return value.toString(8);
	            case 'x':
	                return value.toString(16);
	            case 'X':
	                return value.toString(16).toUpperCase();
	            case 'g':
	                return value.toPrecision(precision);
	            case 'e':
	                return value.toExponential(precision);
	            case 'f':
	                return value.toFixed(precision);
	            case 'r':
	                return (value = this.round(value, this.precision(value, precision))).toFixed(Math.max(0, Math.min(20, this.precision(value * (1 + 1e-15), precision))));
	            default:
	                return value + '';
	        }
	    },

	    round: function(value, precision) {

	        return precision
	            ? Math.round(value * (precision = Math.pow(10, precision))) / precision
	            : Math.round(value);
	    },

	    precision: function(value, precision) {

	        return precision - (value ? Math.ceil(Math.log(value) / Math.LN10) : 1);
	    },

	    prefix: function(value, precision) {

	        var prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'].map(function(d, i) {
	            var k = Math.pow(10, Math.abs(8 - i) * 3);
	            return {
	                scale: i > 8 ? function(d) {
	                    return d / k;
	                } : function(d) {
	                    return d * k;
	                },
	                symbol: d
	            };
	        });

	        var i = 0;
	        if (value) {
	            if (value < 0) { value *= -1; }
	            if (precision) { value = this.round(value, this.precision(value, precision)); }
	            i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
	            i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
	        }
	        return prefixes[8 + i / 3];
	    }
	};

	/*
	    Pre-compile the HTML to be used as a template.
	*/
	var template = function(html) {

	    /*
	        Must support the variation in templating syntax found here:
	        https://lodash.com/docs#template
	    */
	    var regex = /<%= ([^ ]+) %>|\$\{ ?([^{} ]+) ?\}|\{\{([^{} ]+)\}\}/g;

	    return function(data) {

	        data = data || {};

	        return html.replace(regex, function(match) {

	            var args = Array.from(arguments);
	            var attr = args.slice(1, 4).find(function(_attr) {
	                return !!_attr;
	            });

	            var attrArray = attr.split('.');
	            var value = data[attrArray.shift()];

	            while (value !== undefined && attrArray.length) {
	                value = value[attrArray.shift()];
	            }

	            return value !== undefined ? value : '';
	        });
	    };
	};

	/**
	 * @param {Element} el Element, which content is intent to display in full-screen mode, 'window.top.document.body' is default.
	 */
	var toggleFullScreen = function(el) {

	    var topDocument = window.top.document;
	    el = el || topDocument.body;

	    function prefixedResult(el, prop) {

	        var prefixes = ['webkit', 'moz', 'ms', 'o', ''];
	        for (var i = 0; i < prefixes.length; i++) {
	            var prefix = prefixes[i];
	            var propName = prefix ? (prefix + prop) : (prop.substr(0, 1).toLowerCase() + prop.substr(1));
	            if (el[propName] !== undefined) {
	                return isFunction(el[propName]) ? el[propName]() : el[propName];
	            }
	        }
	    }

	    if (prefixedResult(topDocument, 'FullscreenElement') || prefixedResult(topDocument, 'FullScreenElement')) {
	        prefixedResult(topDocument, 'ExitFullscreen') || // Spec.
	        prefixedResult(topDocument, 'CancelFullScreen'); // Firefox
	    } else {
	        prefixedResult(el, 'RequestFullscreen') || // Spec.
	        prefixedResult(el, 'RequestFullScreen'); // Firefox
	    }
	};

	var noop = function() {
	};

	// Clone `cells` returning an object that maps the original cell ID to the clone. The number
	// of clones is exactly the same as the `cells.length`.
	// This function simply clones all the `cells`. However, it also reconstructs
	// all the `source/target` and `parent/embed` references within the `cells`.
	// This is the main difference from the `cell.clone()` method. The
	// `cell.clone()` method works on one single cell only.
	// For example, for a graph: `A --- L ---> B`, `cloneCells([A, L, B])`
	// returns `[A2, L2, B2]` resulting to a graph: `A2 --- L2 ---> B2`, i.e.
	// the source and target of the link `L2` is changed to point to `A2` and `B2`.
	function cloneCells(cells) {

	    cells = uniq(cells);

	    // A map of the form [original cell ID] -> [clone] helping
	    // us to reconstruct references for source/target and parent/embeds.
	    // This is also the returned value.
	    var cloneMap = toArray(cells).reduce(function(map, cell) {
	        map[cell.id] = cell.clone();
	        return map;
	    }, {});

	    toArray(cells).forEach(function(cell) {

	        var clone = cloneMap[cell.id];
	        // assert(clone exists)

	        if (clone.isLink()) {
	            var source = clone.source();
	            var target = clone.target();
	            if (source.id && cloneMap[source.id]) {
	                // Source points to an element and the element is among the clones.
	                // => Update the source of the cloned link.
	                clone.prop('source/id', cloneMap[source.id].id);
	            }
	            if (target.id && cloneMap[target.id]) {
	                // Target points to an element and the element is among the clones.
	                // => Update the target of the cloned link.
	                clone.prop('target/id', cloneMap[target.id].id);
	            }
	        }

	        // Find the parent of the original cell
	        var parent = cell.get('parent');
	        if (parent && cloneMap[parent]) {
	            clone.set('parent', cloneMap[parent].id);
	        }

	        // Find the embeds of the original cell
	        var embeds = toArray(cell.get('embeds')).reduce(function(newEmbeds, embed) {
	            // Embedded cells that are not being cloned can not be carried
	            // over with other embedded cells.
	            if (cloneMap[embed]) {
	                newEmbeds.push(cloneMap[embed].id);
	            }
	            return newEmbeds;
	        }, []);

	        if (!isEmpty(embeds)) {
	            clone.set('embeds', embeds);
	        }
	    });

	    return cloneMap;
	}

	var validPropertiesList = ['checked', 'selected', 'disabled', 'readOnly', 'contentEditable', 'value', 'indeterminate'];

	var validProperties = validPropertiesList.reduce(function (acc, key) {
	    acc[key] = true;
	    return acc;
	}, {});

	var props$1 = {
	    qualify: function(properties) {
	        return isPlainObject(properties);
	    },
	    set: function(properties, _, node) {
	        Object.keys(properties).forEach(function(key) {
	            if (validProperties[key] && key in node) {
	                var value = properties[key];
	                if (node.tagName === 'SELECT' && Array.isArray(value)) {
	                    Array.from(node.options).forEach(function(option, index) {
	                        option.selected = value.includes(option.value);
	                    });
	                } else {
	                    node[key] = value;
	                }
	            }
	        });
	    }
	};

	function setWrapper(attrName, dimension) {
	    return function(value, refBBox) {
	        var isValuePercentage = isPercentage(value);
	        value = parseFloat(value);
	        if (isValuePercentage) {
	            value /= 100;
	        }

	        var attrs = {};
	        if (isFinite(value)) {
	            var attrValue = (isValuePercentage || value >= 0 && value <= 1)
	                ? value * refBBox[dimension]
	                : Math.max(value + refBBox[dimension], 0);
	            attrs[attrName] = attrValue;
	        }

	        return attrs;
	    };
	}

	function positionWrapper(axis, dimension, origin) {
	    return function(value, refBBox) {
	        var valuePercentage = isPercentage(value);
	        value = parseFloat(value);
	        if (valuePercentage) {
	            value /= 100;
	        }

	        var delta;
	        if (isFinite(value)) {
	            var refOrigin = refBBox[origin]();
	            if (valuePercentage || value > 0 && value < 1) {
	                delta = refOrigin[axis] + refBBox[dimension] * value;
	            } else {
	                delta = refOrigin[axis] + value;
	            }
	        }

	        var point = Point();
	        point[axis] = delta || 0;
	        return point;
	    };
	}

	function offsetWrapper(axis, dimension, corner) {
	    return function(value, nodeBBox) {
	        var delta;
	        if (value === 'middle') {
	            delta = nodeBBox[dimension] / 2;
	        } else if (value === corner) {
	            delta = nodeBBox[dimension];
	        } else if (isFinite(value)) {
	            // TODO: or not to do a breaking change?
	            delta = (value > -1 && value < 1) ? (-nodeBBox[dimension] * value) : -value;
	        } else if (isPercentage(value)) {
	            delta = nodeBBox[dimension] * parseFloat(value) / 100;
	        } else {
	            delta = 0;
	        }

	        var point = Point();
	        point[axis] = -(nodeBBox[axis] + delta);
	        return point;
	    };
	}

	function shapeWrapper(shapeConstructor, opt) {
	    var cacheName = 'joint-shape';
	    var resetOffset = opt && opt.resetOffset;
	    return function(value, refBBox, node) {
	        var $node = $(node);
	        var cache = $node.data(cacheName);
	        if (!cache || cache.value !== value) {
	            // only recalculate if value has changed
	            var cachedShape = shapeConstructor(value);
	            cache = {
	                value: value,
	                shape: cachedShape,
	                shapeBBox: cachedShape.bbox()
	            };
	            $node.data(cacheName, cache);
	        }

	        var shape = cache.shape.clone();
	        var shapeBBox = cache.shapeBBox.clone();
	        var shapeOrigin = shapeBBox.origin();
	        var refOrigin = refBBox.origin();

	        shapeBBox.x = refOrigin.x;
	        shapeBBox.y = refOrigin.y;

	        var fitScale = refBBox.maxRectScaleToFit(shapeBBox, refOrigin);
	        // `maxRectScaleToFit` can give Infinity if width or height is 0
	        var sx = (shapeBBox.width === 0 || refBBox.width === 0) ? 1 : fitScale.sx;
	        var sy = (shapeBBox.height === 0 || refBBox.height === 0) ? 1 : fitScale.sy;

	        shape.scale(sx, sy, shapeOrigin);
	        if (resetOffset) {
	            shape.translate(-shapeOrigin.x, -shapeOrigin.y);
	        }

	        return shape;
	    };
	}

	// `d` attribute for SVGPaths
	function dWrapper(opt) {
	    function pathConstructor(value) {
	        return new Path(V.normalizePathData(value));
	    }

	    var shape = shapeWrapper(pathConstructor, opt);
	    return function(value, refBBox, node) {
	        var path = shape(value, refBBox, node);
	        return {
	            d: path.serialize()
	        };
	    };
	}

	// `points` attribute for SVGPolylines and SVGPolygons
	function pointsWrapper(opt) {
	    var shape = shapeWrapper(Polyline, opt);
	    return function(value, refBBox, node) {
	        var polyline = shape(value, refBBox, node);
	        return {
	            points: polyline.serialize()
	        };
	    };
	}

	function atConnectionWrapper(method, opt) {
	    var zeroVector = new Point(1, 0);
	    return function(value) {
	        var p, angle;
	        var tangent = this[method](value);
	        if (tangent) {
	            angle = (opt.rotate) ? tangent.vector().vectorAngle(zeroVector) : 0;
	            p = tangent.start;
	        } else {
	            p = this.path.start;
	            angle = 0;
	        }
	        if (angle === 0) { return { transform: 'translate(' + p.x + ',' + p.y + ')' }; }
	        return { transform: 'translate(' + p.x + ',' + p.y + ') rotate(' + angle + ')' };
	    };
	}

	function setIfChangedWrapper(attribute) {
	    return function setIfChanged(value, _, node) {
	        var vel = V(node);
	        if (vel.attr(attribute) === value) { return; }
	        vel.attr(attribute, value);
	    };
	}

	function isTextInUse(_value, _node, attrs) {
	    return (attrs.text !== undefined);
	}

	function isLinkView() {
	    return this.model.isLink();
	}

	function contextMarker(context) {
	    var marker = {};
	    // Stroke
	    // The context 'fill' is disregared here. The usual case is to use the marker with a connection
	    // (for which 'fill' attribute is set to 'none').
	    var stroke = context.stroke;
	    if (typeof stroke === 'string') {
	        marker['stroke'] = stroke;
	        marker['fill'] = stroke;
	    }
	    // Opacity
	    // Again the context 'fill-opacity' is ignored.
	    var strokeOpacity = context.strokeOpacity;
	    if (strokeOpacity === undefined) { strokeOpacity = context['stroke-opacity']; }
	    if (strokeOpacity === undefined) { strokeOpacity = context.opacity; }
	    if (strokeOpacity !== undefined) {
	        marker['stroke-opacity'] = strokeOpacity;
	        marker['fill-opacity'] = strokeOpacity;
	    }
	    return marker;
	}

	function setPaintURL(def) {
	    var ref = this;
	    var paper = ref.paper;
	    var url = (def.type === 'pattern')
	        ? paper.definePattern(def)
	        : paper.defineGradient(def);
	    return ("url(#" + url + ")");
	}

	var attributesNS = {

	    xlinkShow: {
	        set: 'xlink:show'
	    },

	    xlinkRole: {
	        set: 'xlink:role'
	    },

	    xlinkType: {
	        set: 'xlink:type'
	    },

	    xlinkArcrole: {
	        set: 'xlink:arcrole'
	    },

	    xlinkTitle: {
	        set: 'xlink:title'
	    },

	    xlinkActuate: {
	        set: 'xlink:actuate'
	    },

	    xmlSpace: {
	        set: 'xml:space'
	    },

	    xmlBase: {
	        set: 'xml:base'
	    },

	    xmlLang: {
	        set: 'xml:lang'
	    },

	    preserveAspectRatio: {
	        set: 'preserveAspectRatio'
	    },

	    requiredExtension: {
	        set: 'requiredExtension'
	    },

	    requiredFeatures: {
	        set: 'requiredFeatures'
	    },

	    systemLanguage: {
	        set: 'systemLanguage'
	    },

	    externalResourcesRequired: {
	        set: 'externalResourceRequired'
	    },

	    href: {
	        set: setIfChangedWrapper('href')
	    },

	    xlinkHref: {
	        set: setIfChangedWrapper('xlink:href')
	    },

	    filter: {
	        qualify: isPlainObject,
	        set: function(filter) {
	            return 'url(#' + this.paper.defineFilter(filter) + ')';
	        }
	    },

	    fill: {
	        qualify: isPlainObject,
	        set: setPaintURL
	    },

	    stroke: {
	        qualify: isPlainObject,
	        set: setPaintURL
	    },

	    sourceMarker: {
	        qualify: isPlainObject,
	        set: function(marker, refBBox, node, attrs) {
	            marker = assign(contextMarker(attrs), marker);
	            return { 'marker-start': 'url(#' + this.paper.defineMarker(marker) + ')' };
	        }
	    },

	    targetMarker: {
	        qualify: isPlainObject,
	        set: function(marker, refBBox, node, attrs) {
	            marker = assign(contextMarker(attrs), { 'transform': 'rotate(180)' }, marker);
	            return { 'marker-end': 'url(#' + this.paper.defineMarker(marker) + ')' };
	        }
	    },

	    vertexMarker: {
	        qualify: isPlainObject,
	        set: function(marker, refBBox, node, attrs) {
	            marker = assign(contextMarker(attrs), marker);
	            return { 'marker-mid': 'url(#' + this.paper.defineMarker(marker) + ')' };
	        }
	    },

	    text: {
	        qualify: function(_text, _node, attrs) {
	            return !attrs.textWrap || !isPlainObject(attrs.textWrap);
	        },
	        set: function(text, refBBox, node, attrs) {
	            var $node = $(node);
	            var cacheName = 'joint-text';
	            var cache = $node.data(cacheName);
	            var textAttrs = pick(attrs, 'lineHeight', 'annotations', 'textPath', 'x', 'textVerticalAnchor', 'eol', 'displayEmpty');
	            // eval `x` if using calc()
	            var x = textAttrs.x;
	            if (isCalcAttribute(x)) {
	                textAttrs.x = evalCalcAttribute(x, refBBox);
	            }

	            var fontSizeAttr = attrs['font-size'] || attrs['fontSize'];
	            if (isCalcAttribute(fontSizeAttr)) {
	                fontSizeAttr = evalCalcAttribute(fontSizeAttr, refBBox);
	            }
	            var fontSize = textAttrs.fontSize = fontSizeAttr;
	            var textHash = JSON.stringify([text, textAttrs]);
	            // Update the text only if there was a change in the string
	            // or any of its attributes.
	            if (cache === undefined || cache !== textHash) {
	                // Chrome bug:
	                // Tspans positions defined as `em` are not updated
	                // when container `font-size` change.
	                if (fontSize) { node.setAttribute('font-size', fontSize); }
	                // Text Along Path Selector
	                var textPath = textAttrs.textPath;
	                if (isObject$1(textPath)) {
	                    var pathSelector = textPath.selector;
	                    if (typeof pathSelector === 'string') {
	                        var pathNode = this.findBySelector(pathSelector)[0];
	                        if (pathNode instanceof SVGPathElement) {
	                            textAttrs.textPath = assign({ 'xlink:href': '#' + pathNode.id }, textPath);
	                        }
	                    }
	                }
	                V(node).text('' + text, textAttrs);
	                $node.data(cacheName, textHash);
	            }
	        }
	    },

	    textWrap: {
	        qualify: isPlainObject,
	        set: function(value, refBBox, node, attrs) {
	            var size = {};
	            // option `width`
	            var width = value.width || 0;
	            if (isPercentage(width)) {
	                size.width = refBBox.width * parseFloat(width) / 100;
	            } else if (isCalcAttribute(width)) {
	                size.width = Number(evalCalcAttribute(width, refBBox));
	            } else {
	                if (value.width === null) {
	                    // breakText() requires width to be specified.
	                    size.width = Infinity;
	                } else if (width <= 0) {
	                    size.width = refBBox.width + width;
	                } else {
	                    size.width = width;
	                }
	            }
	            // option `height`
	            var height = value.height || 0;
	            if (isPercentage(height)) {
	                size.height = refBBox.height * parseFloat(height) / 100;
	            } else if (isCalcAttribute(height)) {
	                size.height = Number(evalCalcAttribute(height, refBBox));
	            } else {
	                if (value.height === null) {
	                    // if height is not specified breakText() does not
	                    // restrict the height of the text.
	                } else if (height <= 0) {
	                    size.height = refBBox.height + height;
	                } else {
	                    size.height = height;
	                }
	            }
	            // option `text`
	            var wrappedText;
	            var text = value.text;
	            if (text === undefined) { text = attrs.text; }
	            if (text !== undefined) {
	                var breakTextFn = value.breakText || breakText;
	                var fontSizeAttr = attrs['font-size'] || attrs.fontSize;
	                wrappedText = breakTextFn('' + text, size, {
	                    'font-weight': attrs['font-weight'] || attrs.fontWeight,
	                    'font-size': isCalcAttribute(fontSizeAttr) ? evalCalcAttribute(fontSizeAttr, refBBox) : fontSizeAttr,
	                    'font-family': attrs['font-family'] || attrs.fontFamily,
	                    'lineHeight': attrs.lineHeight,
	                    'letter-spacing': 'letter-spacing' in attrs ? attrs['letter-spacing'] : attrs.letterSpacing
	                }, {
	                    // Provide an existing SVG Document here
	                    // instead of creating a temporary one over again.
	                    svgDocument: this.paper.svg,
	                    ellipsis: value.ellipsis,
	                    hyphen: value.hyphen,
	                    maxLineCount: value.maxLineCount,
	                    preserveSpaces: value.preserveSpaces
	                });
	            } else {
	                wrappedText = '';
	            }
	            attributesNS.text.set.call(this, wrappedText, refBBox, node, attrs);
	        }
	    },

	    title: {
	        qualify: function(title, node) {
	            // HTMLElement title is specified via an attribute (i.e. not an element)
	            return node instanceof SVGElement;
	        },
	        set: function(title, refBBox, node) {
	            var $node = $(node);
	            var cacheName = 'joint-title';
	            var cache = $node.data(cacheName);
	            if (cache === undefined || cache !== title) {
	                $node.data(cacheName, title);
	                if (node.tagName === 'title') {
	                    // The target node is a <title> element.
	                    node.textContent = title;
	                    return;
	                }
	                // Generally <title> element should be the first child element of its parent.
	                var firstChild = node.firstElementChild;
	                if (firstChild && firstChild.tagName === 'title') {
	                    // Update an existing title
	                    firstChild.textContent = title;
	                } else {
	                    // Create a new title
	                    var titleNode = document.createElementNS(node.namespaceURI, 'title');
	                    titleNode.textContent = title;
	                    node.insertBefore(titleNode, firstChild);
	                }
	            }
	        }
	    },

	    lineHeight: {
	        qualify: isTextInUse
	    },

	    textVerticalAnchor: {
	        qualify: isTextInUse
	    },

	    textPath: {
	        qualify: isTextInUse
	    },

	    annotations: {
	        qualify: isTextInUse
	    },

	    eol: {
	        qualify: isTextInUse
	    },

	    displayEmpty: {
	        qualify: isTextInUse
	    },

	    // `port` attribute contains the `id` of the port that the underlying magnet represents.
	    port: {
	        set: function(port) {
	            return (port === null || port.id === undefined) ? port : port.id;
	        }
	    },

	    // `style` attribute is special in the sense that it sets the CSS style of the subelement.
	    style: {
	        qualify: isPlainObject,
	        set: function(styles, refBBox, node) {
	            $(node).css(styles);
	        }
	    },

	    html: {
	        set: function(html, refBBox, node) {
	            $(node).html(html + '');
	        }
	    },

	    // Properties setter (set various properties on the node)
	    props: props$1,

	    ref: {
	        // We do not set `ref` attribute directly on an element.
	        // The attribute itself does not qualify for relative positioning.
	    },

	    // if `refX` is in [0, 1] then `refX` is a fraction of bounding box width
	    // if `refX` is < 0 then `refX`'s absolute values is the right coordinate of the bounding box
	    // otherwise, `refX` is the left coordinate of the bounding box

	    refX: {
	        position: positionWrapper('x', 'width', 'origin')
	    },

	    refY: {
	        position: positionWrapper('y', 'height', 'origin')
	    },

	    // `ref-dx` and `ref-dy` define the offset of the subelement relative to the right and/or bottom
	    // coordinate of the reference element.

	    refDx: {
	        position: positionWrapper('x', 'width', 'corner')
	    },

	    refDy: {
	        position: positionWrapper('y', 'height', 'corner')
	    },

	    // 'ref-width'/'ref-height' defines the width/height of the subelement relatively to
	    // the reference element size
	    // val in 0..1         ref-width = 0.75 sets the width to 75% of the ref. el. width
	    // val < 0 || val > 1  ref-height = -20 sets the height to the ref. el. height shorter by 20

	    refWidth: {
	        set: setWrapper('width', 'width')
	    },

	    refHeight: {
	        set: setWrapper('height', 'height')
	    },

	    refRx: {
	        set: setWrapper('rx', 'width')
	    },

	    refRy: {
	        set: setWrapper('ry', 'height')
	    },

	    refRInscribed: {
	        set: (function(attrName) {
	            var widthFn = setWrapper(attrName, 'width');
	            var heightFn = setWrapper(attrName, 'height');
	            return function(value, refBBox) {
	                var fn = (refBBox.height > refBBox.width) ? widthFn : heightFn;
	                return fn(value, refBBox);
	            };
	        })('r')
	    },

	    refRCircumscribed: {
	        set: function(value, refBBox) {
	            var isValuePercentage = isPercentage(value);
	            value = parseFloat(value);
	            if (isValuePercentage) {
	                value /= 100;
	            }

	            var diagonalLength = Math.sqrt((refBBox.height * refBBox.height) + (refBBox.width * refBBox.width));

	            var rValue;
	            if (isFinite(value)) {
	                if (isValuePercentage || value >= 0 && value <= 1) { rValue = value * diagonalLength; }
	                else { rValue = Math.max(value + diagonalLength, 0); }
	            }

	            return { r: rValue };
	        }
	    },

	    refCx: {
	        set: setWrapper('cx', 'width')
	    },

	    refCy: {
	        set: setWrapper('cy', 'height')
	    },

	    // `x-alignment` when set to `middle` causes centering of the subelement around its new x coordinate.
	    // `x-alignment` when set to `right` uses the x coordinate as referenced to the right of the bbox.

	    xAlignment: {
	        offset: offsetWrapper('x', 'width', 'right')
	    },

	    // `y-alignment` when set to `middle` causes centering of the subelement around its new y coordinate.
	    // `y-alignment` when set to `bottom` uses the y coordinate as referenced to the bottom of the bbox.

	    yAlignment: {
	        offset: offsetWrapper('y', 'height', 'bottom')
	    },

	    resetOffset: {
	        offset: function(val, nodeBBox) {
	            return (val)
	                ? { x: -nodeBBox.x, y: -nodeBBox.y }
	                : { x: 0, y: 0 };
	        }

	    },

	    refDResetOffset: {
	        set: dWrapper({ resetOffset: true })
	    },

	    refDKeepOffset: {
	        set: dWrapper({ resetOffset: false })
	    },

	    refPointsResetOffset: {
	        set: pointsWrapper({ resetOffset: true })
	    },

	    refPointsKeepOffset: {
	        set: pointsWrapper({ resetOffset: false })
	    },

	    // LinkView Attributes

	    connection: {
	        qualify: isLinkView,
	        set: function(ref) {
	            var stubs = ref.stubs; if ( stubs === void 0 ) stubs = 0;

	            var d;
	            if (isFinite(stubs) && stubs !== 0) {
	                var offset;
	                if (stubs < 0) {
	                    offset = (this.getConnectionLength() + stubs) / 2;
	                } else {
	                    offset = stubs;
	                }
	                var path = this.getConnection();
	                var segmentSubdivisions = this.getConnectionSubdivisions();
	                var sourceParts = path.divideAtLength(offset, { segmentSubdivisions: segmentSubdivisions });
	                var targetParts = path.divideAtLength(-offset, { segmentSubdivisions: segmentSubdivisions });
	                if (sourceParts && targetParts) {
	                    d = (sourceParts[0].serialize()) + " " + (targetParts[1].serialize());
	                }
	            }

	            return { d: d || this.getSerializedConnection() };
	        }
	    },

	    atConnectionLengthKeepGradient: {
	        qualify: isLinkView,
	        set: atConnectionWrapper('getTangentAtLength', { rotate: true })
	    },

	    atConnectionLengthIgnoreGradient: {
	        qualify: isLinkView,
	        set: atConnectionWrapper('getTangentAtLength', { rotate: false })
	    },

	    atConnectionRatioKeepGradient: {
	        qualify: isLinkView,
	        set: atConnectionWrapper('getTangentAtRatio', { rotate: true })
	    },

	    atConnectionRatioIgnoreGradient: {
	        qualify: isLinkView,
	        set: atConnectionWrapper('getTangentAtRatio', { rotate: false })
	    }
	};

	attributesNS['xlink:href'] = attributesNS.xlinkHref;

	// Support `calc()` with the following SVG attributes
	[
	    'transform', // g
	    'd', // path
	    'points', // polyline / polygon
	    'cx', 'cy', // circle / ellipse
	    'x1', 'x2', 'y1', 'y2', // line
	    'x', 'y', // rect / text / image
	    'dx', 'dy' // text
	].forEach(function (attribute) {
	    attributesNS[attribute] = {
	        qualify: isCalcAttribute,
	        set: function setCalcAttribute(value, refBBox) {
	            var obj;

	            return ( obj = {}, obj[attribute] = evalCalcAttribute(value, refBBox), obj );
	        }
	    };
	});

	// Prevent "A negative value is not valid" error.
	[
	    'width', 'height', // rect / image
	    'r', // circle
	    'rx', 'ry', // rect / ellipse
	    'font-size', // text
	    'stroke-width' // elements
	].forEach(function (attribute) {
	    attributesNS[attribute] = {
	        qualify: isCalcAttribute,
	        set: function setCalcAttribute(value, refBBox) {
	            var obj;

	            return ( obj = {}, obj[attribute] = Math.max(0, evalCalcAttribute(value, refBBox)), obj );
	        }
	    };
	});

	// Aliases
	attributesNS.refR = attributesNS.refRInscribed;
	attributesNS.refD = attributesNS.refDResetOffset;
	attributesNS.refPoints = attributesNS.refPointsResetOffset;
	attributesNS.atConnectionLength = attributesNS.atConnectionLengthKeepGradient;
	attributesNS.atConnectionRatio = attributesNS.atConnectionRatioKeepGradient;
	attributesNS.fontSize = attributesNS['font-size'];
	attributesNS.strokeWidth = attributesNS['stroke-width'];

	// This allows to combine both absolute and relative positioning
	// refX: 50%, refX2: 20
	attributesNS.refX2 = attributesNS.refX;
	attributesNS.refY2 = attributesNS.refY;
	attributesNS.refWidth2 = attributesNS.refWidth;
	attributesNS.refHeight2 = attributesNS.refHeight;

	// Aliases for backwards compatibility
	attributesNS['ref-x'] = attributesNS.refX;
	attributesNS['ref-y'] = attributesNS.refY;
	attributesNS['ref-dy'] = attributesNS.refDy;
	attributesNS['ref-dx'] = attributesNS.refDx;
	attributesNS['ref-width'] = attributesNS.refWidth;
	attributesNS['ref-height'] = attributesNS.refHeight;
	attributesNS['x-alignment'] = attributesNS.xAlignment;
	attributesNS['y-alignment'] = attributesNS.yAlignment;

	var attributes = attributesNS;

	// Cell base model.
	// --------------------------

	var Cell = Backbone.Model.extend({

	    // This is the same as Backbone.Model with the only difference that is uses util.merge
	    // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes.
	    constructor: function(attributes, options) {

	        var defaults;
	        var attrs = attributes || {};
	        if (typeof this.preinitialize === 'function') {
	            // Check to support an older version of Backbone (prior v1.4)
	            this.preinitialize.apply(this, arguments);
	        }
	        this.cid = uniqueId('c');
	        this.attributes = {};
	        if (options && options.collection) { this.collection = options.collection; }
	        if (options && options.parse) { attrs = this.parse(attrs, options) || {}; }
	        if ((defaults = result(this, 'defaults'))) {
	            //<custom code>
	            // Replaced the call to _.defaults with util.merge.
	            attrs = merge({}, defaults, attrs);
	            //</custom code>
	        }
	        this.set(attrs, options);
	        this.changed = {};
	        this.initialize.apply(this, arguments);
	    },

	    translate: function(dx, dy, opt) {

	        throw new Error('Must define a translate() method.');
	    },

	    toJSON: function() {

	        var defaults = result(this.constructor.prototype, 'defaults');
	        var defaultAttrs = defaults.attrs || {};
	        var attrs = this.attributes.attrs;
	        var finalAttrs = {};

	        // Loop through all the attributes and
	        // omit the default attributes as they are implicitly reconstructible by the cell 'type'.
	        forIn(attrs, function(attr, selector) {

	            var defaultAttr = defaultAttrs[selector];

	            forIn(attr, function(value, name) {

	                // attr is mainly flat though it might have one more level (consider the `style` attribute).
	                // Check if the `value` is object and if yes, go one level deep.
	                if (isObject$1(value) && !Array.isArray(value)) {

	                    forIn(value, function(value2, name2) {

	                        if (!defaultAttr || !defaultAttr[name] || !isEqual(defaultAttr[name][name2], value2)) {

	                            finalAttrs[selector] = finalAttrs[selector] || {};
	                            (finalAttrs[selector][name] || (finalAttrs[selector][name] = {}))[name2] = value2;
	                        }
	                    });

	                } else if (!defaultAttr || !isEqual(defaultAttr[name], value)) {
	                    // `value` is not an object, default attribute for such a selector does not exist
	                    // or it is different than the attribute value set on the model.

	                    finalAttrs[selector] = finalAttrs[selector] || {};
	                    finalAttrs[selector][name] = value;
	                }
	            });
	        });

	        var attributes = cloneDeep(omit(this.attributes, 'attrs'));
	        attributes.attrs = finalAttrs;

	        return attributes;
	    },

	    initialize: function(options) {

	        var idAttribute = this.getIdAttribute();
	        if (!options || options[idAttribute] === undefined) {
	            this.set(idAttribute, this.generateId(), { silent: true });
	        }

	        this._transitionIds = {};
	        this._scheduledTransitionIds = {};

	        // Collect ports defined in `attrs` and keep collecting whenever `attrs` object changes.
	        this.processPorts();
	        this.on('change:attrs', this.processPorts, this);
	    },

	    getIdAttribute: function() {
	        return this.idAttribute || 'id';
	    },

	    generateId: function() {
	        return uuid();
	    },

	    /**
	     * @deprecated
	     */
	    processPorts: function() {

	        // Whenever `attrs` changes, we extract ports from the `attrs` object and store it
	        // in a more accessible way. Also, if any port got removed and there were links that had `target`/`source`
	        // set to that port, we remove those links as well (to follow the same behaviour as
	        // with a removed element).

	        var previousPorts = this.ports;

	        // Collect ports from the `attrs` object.
	        var ports = {};
	        forIn(this.get('attrs'), function(attrs, selector) {

	            if (attrs && attrs.port) {

	                // `port` can either be directly an `id` or an object containing an `id` (and potentially other data).
	                if (attrs.port.id !== undefined) {
	                    ports[attrs.port.id] = attrs.port;
	                } else {
	                    ports[attrs.port] = { id: attrs.port };
	                }
	            }
	        });

	        // Collect ports that have been removed (compared to the previous ports) - if any.
	        // Use hash table for quick lookup.
	        var removedPorts = {};
	        forIn(previousPorts, function(port, id) {

	            if (!ports[id]) { removedPorts[id] = true; }
	        });

	        // Remove all the incoming/outgoing links that have source/target port set to any of the removed ports.
	        if (this.graph && !isEmpty(removedPorts)) {

	            var inboundLinks = this.graph.getConnectedLinks(this, { inbound: true });
	            inboundLinks.forEach(function(link) {

	                if (removedPorts[link.get('target').port]) { link.remove(); }
	            });

	            var outboundLinks = this.graph.getConnectedLinks(this, { outbound: true });
	            outboundLinks.forEach(function(link) {

	                if (removedPorts[link.get('source').port]) { link.remove(); }
	            });
	        }

	        // Update the `ports` object.
	        this.ports = ports;
	    },

	    remove: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        // Store the graph in a variable because `this.graph` won't be accessible
	        // after `this.trigger('remove', ...)` down below.
	        var ref = this;
	        var graph = ref.graph;
	        var collection = ref.collection;
	        if (!graph) {
	            // The collection is a common Backbone collection (not the graph collection).
	            if (collection) { collection.remove(this, opt); }
	            return this;
	        }

	        graph.startBatch('remove');

	        // First, unembed this cell from its parent cell if there is one.
	        var parentCell = this.getParentCell();
	        if (parentCell) {
	            parentCell.unembed(this, opt);
	        }

	        // Remove also all the cells, which were embedded into this cell
	        var embeddedCells = this.getEmbeddedCells();
	        for (var i = 0, n = embeddedCells.length; i < n; i++) {
	            var embed = embeddedCells[i];
	            if (embed) {
	                embed.remove(opt);
	            }
	        }

	        this.trigger('remove', this, graph.attributes.cells, opt);

	        graph.stopBatch('remove');

	        return this;
	    },

	    toFront: function(opt) {
	        var graph = this.graph;
	        if (graph) {
	            opt = opt || {};

	            var cells;
	            if (opt.deep) {
	                cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false });
	                cells.unshift(this);
	            } else {
	                cells = [this];
	            }

	            var sortedCells = sortBy(cells, function (cell) { return cell.z(); });

	            var maxZ = graph.maxZIndex();
	            var z = maxZ - cells.length + 1;

	            var collection = graph.get('cells');

	            var shouldUpdate = (collection.indexOf(this) !== (collection.length - cells.length));
	            if (!shouldUpdate) {
	                shouldUpdate = sortedCells.some(function(cell, index) {
	                    return cell.z() !== z + index;
	                });
	            }

	            if (shouldUpdate) {
	                this.startBatch('to-front');

	                z = z + cells.length;

	                sortedCells.forEach(function(cell, index) {
	                    cell.set('z', z + index, opt);
	                });

	                this.stopBatch('to-front');
	            }
	        }

	        return this;
	    },

	    toBack: function(opt) {
	        var graph = this.graph;
	        if (graph) {
	            opt = opt || {};

	            var cells;
	            if (opt.deep) {
	                cells = this.getEmbeddedCells({ deep: true, breadthFirst: opt.breadthFirst !== false });
	                cells.unshift(this);
	            } else {
	                cells = [this];
	            }

	            var sortedCells = sortBy(cells, function (cell) { return cell.z(); });

	            var z = graph.minZIndex();

	            var collection = graph.get('cells');

	            var shouldUpdate = (collection.indexOf(this) !== 0);
	            if (!shouldUpdate) {
	                shouldUpdate = sortedCells.some(function(cell, index) {
	                    return cell.z() !== z + index;
	                });
	            }

	            if (shouldUpdate) {
	                this.startBatch('to-back');

	                z -= cells.length;

	                sortedCells.forEach(function(cell, index) {
	                    cell.set('z', z + index, opt);
	                });

	                this.stopBatch('to-back');
	            }
	        }

	        return this;
	    },

	    parent: function(parent, opt) {

	        // getter
	        if (parent === undefined) { return this.get('parent'); }
	        // setter
	        return this.set('parent', parent, opt);
	    },

	    embed: function(cell, opt) {
	        var this$1 = this;

	        var cells = Array.isArray(cell) ? cell : [cell];
	        if (!this.canEmbed(cells)) {
	            throw new Error('Recursive embedding not allowed.');
	        }
	        if (cells.some(function (c) { return c.isEmbedded() && this$1.id !== c.parent(); })) {
	            throw new Error('Embedding of already embedded cells is not allowed.');
	        }
	        this._embedCells(cells, opt);
	        return this;
	    },

	    unembed: function(cell, opt) {
	        var cells = Array.isArray(cell) ? cell : [cell];
	        this._unembedCells(cells, opt);
	        return this;
	    },

	    canEmbed: function(cell) {
	        var this$1 = this;

	        var cells = Array.isArray(cell) ? cell : [cell];
	        return cells.every(function (c) { return this$1 !== c && !this$1.isEmbeddedIn(c); });
	    },

	    _embedCells: function(cells, opt) {
	        var this$1 = this;

	        var batchName = 'embed';
	        this.startBatch(batchName);
	        var embeds = assign([], this.get('embeds'));
	        cells.forEach(function (cell) {
	            // We keep all element ids after link ids.
	            embeds[cell.isLink() ? 'unshift' : 'push'](cell.id);
	            cell.parent(this$1.id, opt);
	        });
	        this.set('embeds', uniq(embeds), opt);
	        this.stopBatch(batchName);
	    },

	    _unembedCells: function(cells, opt) {
	        var batchName = 'unembed';
	        this.startBatch(batchName);
	        cells.forEach(function (cell) { return cell.unset('parent', opt); });
	        this.set('embeds', without.apply(void 0, [ this.get('embeds') ].concat( cells.map(function (cell) { return cell.id; }) )), opt);
	        this.stopBatch(batchName);
	    },

	    getParentCell: function() {

	        // unlike link.source/target, cell.parent stores id directly as a string
	        var parentId = this.parent();
	        var graph = this.graph;

	        return (parentId && graph && graph.getCell(parentId)) || null;
	    },

	    // Return an array of ancestor cells.
	    // The array is ordered from the parent of the cell
	    // to the most distant ancestor.
	    getAncestors: function() {

	        var ancestors = [];

	        if (!this.graph) {
	            return ancestors;
	        }

	        var parentCell = this.getParentCell();
	        while (parentCell) {
	            ancestors.push(parentCell);
	            parentCell = parentCell.getParentCell();
	        }

	        return ancestors;
	    },

	    getEmbeddedCells: function(opt) {

	        opt = opt || {};

	        // Cell models can only be retrieved when this element is part of a collection.
	        // There is no way this element knows about other cells otherwise.
	        // This also means that calling e.g. `translate()` on an element with embeds before
	        // adding it to a graph does not translate its embeds.
	        if (this.graph) {

	            var cells;

	            if (opt.deep) {

	                if (opt.breadthFirst) {

	                    // breadthFirst algorithm
	                    cells = [];
	                    var queue = this.getEmbeddedCells();

	                    while (queue.length > 0) {

	                        var parent = queue.shift();
	                        cells.push(parent);
	                        queue.push.apply(queue, parent.getEmbeddedCells());
	                    }

	                } else {

	                    // depthFirst algorithm
	                    cells = this.getEmbeddedCells();
	                    cells.forEach(function(cell) {
	                        cells.push.apply(cells, cell.getEmbeddedCells(opt));
	                    });
	                }

	            } else {

	                cells = toArray(this.get('embeds')).map(this.graph.getCell, this.graph);
	            }

	            return cells;
	        }
	        return [];
	    },

	    isEmbeddedIn: function(cell, opt) {

	        var cellId = isString(cell) ? cell : cell.id;
	        var parentId = this.parent();

	        opt = assign({ deep: true }, opt);

	        // See getEmbeddedCells().
	        if (this.graph && opt.deep) {

	            while (parentId) {
	                if (parentId === cellId) {
	                    return true;
	                }
	                parentId = this.graph.getCell(parentId).parent();
	            }

	            return false;

	        } else {

	            // When this cell is not part of a collection check
	            // at least whether it's a direct child of given cell.
	            return parentId === cellId;
	        }
	    },

	    // Whether or not the cell is embedded in any other cell.
	    isEmbedded: function() {

	        return !!this.parent();
	    },

	    // Isolated cloning. Isolated cloning has two versions: shallow and deep (pass `{ deep: true }` in `opt`).
	    // Shallow cloning simply clones the cell and returns a new cell with different ID.
	    // Deep cloning clones the cell and all its embedded cells recursively.
	    clone: function(opt) {

	        opt = opt || {};

	        if (!opt.deep) {
	            // Shallow cloning.

	            var clone = Backbone.Model.prototype.clone.apply(this, arguments);
	            // We don't want the clone to have the same ID as the original.
	            clone.set(this.getIdAttribute(), this.generateId());
	            // A shallow cloned element does not carry over the original embeds.
	            clone.unset('embeds');
	            // And can not be embedded in any cell
	            // as the clone is not part of the graph.
	            clone.unset('parent');

	            return clone;

	        } else {
	            // Deep cloning.

	            // For a deep clone, simply call `graph.cloneCells()` with the cell and all its embedded cells.
	            return toArray(cloneCells([this].concat(this.getEmbeddedCells({ deep: true }))));
	        }
	    },

	    // A convenient way to set nested properties.
	    // This method merges the properties you'd like to set with the ones
	    // stored in the cell and makes sure change events are properly triggered.
	    // You can either set a nested property with one object
	    // or use a property path.
	    // The most simple use case is:
	    // `cell.prop('name/first', 'John')` or
	    // `cell.prop({ name: { first: 'John' } })`.
	    // Nested arrays are supported too:
	    // `cell.prop('series/0/data/0/degree', 50)` or
	    // `cell.prop({ series: [ { data: [ { degree: 50 } ] } ] })`.
	    prop: function(props, value, opt) {

	        var delim = '/';
	        var _isString = isString(props);

	        if (_isString || Array.isArray(props)) {
	            // Get/set an attribute by a special path syntax that delimits
	            // nested objects by the colon character.

	            if (arguments.length > 1) {

	                var path;
	                var pathArray;

	                if (_isString) {
	                    path = props;
	                    pathArray = path.split('/');
	                } else {
	                    path = props.join(delim);
	                    pathArray = props.slice();
	                }

	                var property = pathArray[0];
	                var pathArrayLength = pathArray.length;

	                var options$1 = opt || {};
	                options$1.propertyPath = path;
	                options$1.propertyValue = value;
	                options$1.propertyPathArray = pathArray;
	                if (!('rewrite' in options$1)) {
	                    options$1.rewrite = false;
	                }

	                var update = {};
	                // Initialize the nested object. Sub-objects are either arrays or objects.
	                // An empty array is created if the sub-key is an integer. Otherwise, an empty object is created.
	                // Note that this imposes a limitation on object keys one can use with Inspector.
	                // Pure integer keys will cause issues and are therefore not allowed.
	                var initializer = update;
	                var prevProperty = property;

	                for (var i = 1; i < pathArrayLength; i++) {
	                    var pathItem = pathArray[i];
	                    var isArrayIndex = Number.isFinite(_isString ? Number(pathItem) : pathItem);
	                    initializer = initializer[prevProperty] = isArrayIndex ? [] : {};
	                    prevProperty = pathItem;
	                }

	                // Fill update with the `value` on `path`.
	                update = setByPath(update, pathArray, value, '/');

	                var baseAttributes = merge({}, this.attributes);
	                // if rewrite mode enabled, we replace value referenced by path with
	                // the new one (we don't merge).
	                options$1.rewrite && unsetByPath(baseAttributes, path, '/');

	                // Merge update with the model attributes.
	                var attributes = merge(baseAttributes, update);
	                // Finally, set the property to the updated attributes.
	                return this.set(property, attributes[property], options$1);

	            } else {

	                return getByPath(this.attributes, props, delim);
	            }
	        }

	        var options = value || {};
	        // Note: '' is not the path to the root. It's a path with an empty string i.e. { '': {}}.
	        options.propertyPath = null;
	        options.propertyValue = props;
	        options.propertyPathArray = [];
	        if (!('rewrite' in options)) {
	            options.rewrite = false;
	        }

	        return this.set(merge({}, this.attributes, props), options);
	    },

	    // A convenient way to unset nested properties
	    removeProp: function(path, opt) {

	        opt = opt || {};

	        var pathArray = Array.isArray(path) ? path : path.split('/');

	        // Once a property is removed from the `attrs` attribute
	        // the cellView will recognize a `dirty` flag and re-render itself
	        // in order to remove the attribute from SVG element.
	        var property = pathArray[0];
	        if (property === 'attrs') { opt.dirty = true; }

	        if (pathArray.length === 1) {
	            // A top level property
	            return this.unset(path, opt);
	        }

	        // A nested property
	        var nestedPath = pathArray.slice(1);
	        var propertyValue = this.get(property);
	        if (propertyValue === undefined || propertyValue === null) { return this; }
	        propertyValue = cloneDeep(propertyValue);

	        unsetByPath(propertyValue, nestedPath, '/');

	        return this.set(property, propertyValue, opt);
	    },

	    // A convenient way to set nested attributes.
	    attr: function(attrs, value, opt) {

	        var args = Array.from(arguments);
	        if (args.length === 0) {
	            return this.get('attrs');
	        }

	        if (Array.isArray(attrs)) {
	            args[0] = ['attrs'].concat(attrs);
	        } else if (isString(attrs)) {
	            // Get/set an attribute by a special path syntax that delimits
	            // nested objects by the colon character.
	            args[0] = 'attrs/' + attrs;

	        } else {

	            args[0] = { 'attrs' : attrs };
	        }

	        return this.prop.apply(this, args);
	    },

	    // A convenient way to unset nested attributes
	    removeAttr: function(path, opt) {

	        if (Array.isArray(path)) {

	            return this.removeProp(['attrs'].concat(path));
	        }

	        return this.removeProp('attrs/' + path, opt);
	    },

	    transition: function(path, value, opt, delim) {
	        var this$1 = this;


	        delim = delim || '/';

	        var defaults = {
	            duration: 100,
	            delay: 10,
	            timingFunction: timing.linear,
	            valueFunction: interpolate.number
	        };

	        opt = assign(defaults, opt);

	        var firstFrameTime = 0;
	        var interpolatingFunction;

	        var setter = function(runtime) {

	            var id, progress, propertyValue;

	            firstFrameTime = firstFrameTime || runtime;
	            runtime -= firstFrameTime;
	            progress = runtime / opt.duration;

	            if (progress < 1) {
	                this._transitionIds[path] = id = nextFrame(setter);
	            } else {
	                progress = 1;
	                delete this._transitionIds[path];
	            }

	            propertyValue = interpolatingFunction(opt.timingFunction(progress));

	            opt.transitionId = id;

	            this.prop(path, propertyValue, opt);

	            if (!id) { this.trigger('transition:end', this, path); }

	        }.bind(this);

	        var ref = this;
	        var _scheduledTransitionIds = ref._scheduledTransitionIds;
	        var initialId;

	        var initiator = function (callback) {

	            if (_scheduledTransitionIds[path]) {
	                _scheduledTransitionIds[path] = without(_scheduledTransitionIds[path], initialId);
	                if (_scheduledTransitionIds[path].length === 0) {
	                    delete _scheduledTransitionIds[path];
	                }
	            }

	            this$1.stopPendingTransitions(path, delim);

	            interpolatingFunction = opt.valueFunction(getByPath(this$1.attributes, path, delim), value);

	            this$1._transitionIds[path] = nextFrame(callback);

	            this$1.trigger('transition:start', this$1, path);

	        };

	        initialId = setTimeout(initiator, opt.delay, setter);

	        _scheduledTransitionIds[path] || (_scheduledTransitionIds[path] = []);
	        _scheduledTransitionIds[path].push(initialId);

	        return initialId;
	    },

	    getTransitions: function() {
	        return union(
	            Object.keys(this._transitionIds),
	            Object.keys(this._scheduledTransitionIds)
	        );
	    },

	    stopScheduledTransitions: function(path, delim) {
	        if ( delim === void 0 ) delim = '/';

	        var ref = this;
	        var _scheduledTransitionIds = ref._scheduledTransitionIds; if ( _scheduledTransitionIds === void 0 ) _scheduledTransitionIds = {};
	        var transitions = Object.keys(_scheduledTransitionIds);
	        if (path) {
	            var pathArray = path.split(delim);
	            transitions = transitions.filter(function (key) {
	                return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
	            });
	        }
	        transitions.forEach(function (key) {
	            var transitionIds = _scheduledTransitionIds[key];
	            // stop the initiator
	            transitionIds.forEach(function (transitionId) { return clearTimeout(transitionId); });
	            delete _scheduledTransitionIds[key];
	            // Note: we could trigger transition:cancel` event here
	        });
	        return this;
	    },

	    stopPendingTransitions: function stopPendingTransitions(path, delim) {
	        var this$1 = this;
	        if ( delim === void 0 ) delim = '/';

	        var ref = this;
	        var _transitionIds = ref._transitionIds; if ( _transitionIds === void 0 ) _transitionIds = {};
	        var transitions = Object.keys(_transitionIds);
	        if (path) {
	            var pathArray = path.split(delim);
	            transitions = transitions.filter(function (key) {
	                return isEqual(pathArray, key.split(delim).slice(0, pathArray.length));
	            });
	        }
	        transitions.forEach(function (key) {
	            var transitionId = _transitionIds[key];
	            // stop the setter
	            cancelFrame(transitionId);
	            delete _transitionIds[key];
	            this$1.trigger('transition:end', this$1, key);
	        });
	    },

	    stopTransitions: function(path, delim) {
	        if ( delim === void 0 ) delim = '/';

	        this.stopScheduledTransitions(path, delim);
	        this.stopPendingTransitions(path, delim);
	        return this;
	    },

	    // A shorcut making it easy to create constructs like the following:
	    // `var el = (new joint.shapes.basic.Rect).addTo(graph)`.
	    addTo: function(graph, opt) {

	        graph.addCell(this, opt);
	        return this;
	    },

	    // A shortcut for an equivalent call: `paper.findViewByModel(cell)`
	    // making it easy to create constructs like the following:
	    // `cell.findView(paper).highlight()`
	    findView: function(paper) {

	        return paper.findViewByModel(this);
	    },

	    isElement: function() {

	        return false;
	    },

	    isLink: function() {

	        return false;
	    },

	    startBatch: function(name, opt) {

	        if (this.graph) { this.graph.startBatch(name, assign({}, opt, { cell: this })); }
	        return this;
	    },

	    stopBatch: function(name, opt) {

	        if (this.graph) { this.graph.stopBatch(name, assign({}, opt, { cell: this })); }
	        return this;
	    },

	    getChangeFlag: function(attributes) {

	        var flag = 0;
	        if (!attributes) { return flag; }
	        for (var key in attributes) {
	            if (!attributes.hasOwnProperty(key) || !this.hasChanged(key)) { continue; }
	            flag |= attributes[key];
	        }
	        return flag;
	    },

	    angle: function() {

	        // To be overridden.
	        return 0;
	    },

	    position: function() {

	        // To be overridden.
	        return new Point(0, 0);
	    },

	    z: function() {
	        return this.get('z') || 0;
	    },

	    getPointFromConnectedLink: function() {

	        // To be overridden
	        return new Point();
	    },

	    getBBox: function() {

	        // To be overridden
	        return new Rect(0, 0, 0, 0);
	    },

	    getPointRotatedAroundCenter: function getPointRotatedAroundCenter(angle, x, y) {
	        var point = new Point(x, y);
	        if (angle) { point.rotate(this.getBBox().center(), angle); }
	        return point;
	    },

	    getAbsolutePointFromRelative: function getAbsolutePointFromRelative(x, y) {
	        // Rotate the position to take the model angle into account
	        return this.getPointRotatedAroundCenter(
	            -this.angle(),
	            // Transform the relative position to absolute
	            this.position().offset(x, y)
	        );
	    },

	    getRelativePointFromAbsolute: function getRelativePointFromAbsolute(x, y) {
	        return this
	            // Rotate the coordinates to mitigate the element's rotation.
	            .getPointRotatedAroundCenter(this.angle(), x, y)
	            // Transform the absolute position into relative
	            .difference(this.position());
	    }

	}, {

	    getAttributeDefinition: function(attrName) {

	        var defNS = this.attributes;
	        var globalDefNS = attributes;
	        return (defNS && defNS[attrName]) || globalDefNS[attrName];
	    },

	    define: function(type, defaults, protoProps, staticProps) {

	        protoProps = assign({
	            defaults: defaultsDeep({ type: type }, defaults, this.prototype.defaults)
	        }, protoProps);

	        var Cell = this.extend(protoProps, staticProps);
	        // es5 backward compatibility
	        /* eslint-disable no-undef */
	        if (typeof joint !== 'undefined' && has$2(joint, 'shapes')) {
	            setByPath(joint.shapes, type, Cell, '.');
	        }
	        /* eslint-enable no-undef */
	        return Cell;
	    }
	});

	var wrapWith = function(object, methods, wrapper) {

	    if (isString(wrapper)) {

	        if (!wrappers[wrapper]) {
	            throw new Error('Unknown wrapper: "' + wrapper + '"');
	        }

	        wrapper = wrappers[wrapper];
	    }

	    if (!isFunction(wrapper)) {
	        throw new Error('Wrapper must be a function.');
	    }

	    toArray(methods).forEach(function(method) {
	        object[method] = wrapper(object[method]);
	    });
	};

	var wrappers = {

	    cells: function(fn) {

	        return function() {

	            var args = Array.from(arguments);
	            var n = args.length;
	            var cells = n > 0 && args[0] || [];
	            var opt = n > 1 && args[n - 1] || {};

	            if (!Array.isArray(cells)) {

	                if (opt instanceof Cell) {
	                    cells = args;
	                } else if (cells instanceof Cell) {
	                    if (args.length > 1) {
	                        args.pop();
	                    }
	                    cells = args;
	                }
	            }

	            if (opt instanceof Cell) {
	                opt = {};
	            }

	            return fn.call(this, cells, opt);
	        };
	    }

	};

	function svg(strings) {
	    var expressions = [], len = arguments.length - 1;
	    while ( len-- > 0 ) expressions[ len ] = arguments[ len + 1 ];

	    var svgParts = [];
	    strings.forEach(function (part, index) {
	        svgParts.push(part);
	        if (index in expressions) {
	            svgParts.push(expressions[index]);
	        }
	    });
	    var markup = parseFromSVGString(svgParts.join(''));
	    return markup;
	}

	function parseFromSVGString(str) {
	    var parser = new DOMParser();
	    var markupString = "<svg>" + (str.trim()) + "</svg>";
	    var xmldocument = parser.parseFromString(markupString.replace(/@/g, ''), 'application/xml');
	    if (xmldocument.getElementsByTagName('parsererror')[0]) {
	        throw new Error('Invalid SVG markup');
	    }
	    var document = parser.parseFromString(markupString, 'text/html');
	    var svg = document.querySelector('svg');
	    return build(svg);
	}

	function buildNode(node) {
	    var markupNode = {};
	    var tagName = node.tagName;
	    var attributes = node.attributes;
	    var namespaceURI = node.namespaceURI;
	    var style = node.style;
	    var childNodes = node.childNodes;

	    markupNode.namespaceURI = namespaceURI;
	    markupNode.tagName = (namespaceURI === V.namespace.xhtml)
	        // XHTML documents must use lower case for all HTML element and attribute names.
	        // The tagName property returns upper case value for HTML elements.
	        // e.g. <DIV> vs.<div/>
	        ? tagName.toLowerCase()
	        : tagName;

	    var stylesObject = {};
	    for (var i = style.length; i--;) {
	        var nameString = style[i];
	        stylesObject[nameString] = style.getPropertyValue(nameString);
	    }
	    markupNode.style = stylesObject;

	    // selector fallbacks to tagName
	    var selectorAttribute = attributes.getNamedItem('@selector');
	    if (selectorAttribute) {
	        markupNode.selector = selectorAttribute.value;
	        attributes.removeNamedItem('@selector');
	    }

	    var groupSelectorAttribute = attributes.getNamedItem('@group-selector');
	    if (groupSelectorAttribute) {
	        var groupSelectors = groupSelectorAttribute.value.split(',');
	        markupNode.groupSelector = groupSelectors.map(function (s) { return s.trim(); });

	        attributes.removeNamedItem('@group-selector');
	    }

	    var className = attributes.getNamedItem('class');
	    if (className) {
	        markupNode.className = className.value;
	    }

	    var children = [];
	    childNodes.forEach(function (node) {
	        switch (node.nodeType) {
	            case Node.TEXT_NODE: {
	                var trimmedText = node.data.replace(/\s\s+/g, ' ');
	                if (trimmedText.trim()) {
	                    children.push(trimmedText);
	                }
	                break;
	            }
	            case Node.ELEMENT_NODE: {
	                children.push(buildNode(node));
	                break;
	            }
	            default:
	                break;
	        }
	    });
	    if (children.length) {
	        markupNode.children = children;
	    }

	    var nodeAttrs = {};

	    Array.from(attributes).forEach(function (nodeAttribute) {
	        var name = nodeAttribute.name;
	        var value = nodeAttribute.value;
	        nodeAttrs[name] = value;
	    });

	    if (Object.keys(nodeAttrs).length > 0) {
	        markupNode.attributes = nodeAttrs;
	    }

	    return markupNode;
	}

	function build(root) {
	    var markup = [];

	    Array.from(root.children).forEach(function (node) {
	        markup.push(buildNode(node));
	    });

	    return markup;
	}

	var Positions = {
	    TOP: 'top',
	    RIGHT: 'right',
	    BOTTOM: 'bottom',
	    LEFT: 'left',
	    TOP_LEFT: 'top-left',
	    TOP_RIGHT: 'top-right',
	    BOTTOM_LEFT: 'bottom-left',
	    BOTTOM_RIGHT: 'bottom-right',
	    CENTER: 'center',
	};

	function getRectPoint(rect, position) {
	    var r = new Rect(rect);
	    switch (position) {
	        case undefined:
	            throw new Error('Position required');

	        // Middle Points
	        case Positions.LEFT:
	        case 'leftMiddle':
	            return r.leftMiddle();

	        case Positions.RIGHT:
	        case 'rightMiddle':
	            return r.rightMiddle();

	        case Positions.TOP:
	        case 'topMiddle':
	            return r.topMiddle();

	        case Positions.BOTTOM:
	        case 'bottomMiddle':
	            return r.bottomMiddle();

	        // Corners
	        case Positions.TOP_LEFT:
	        case 'topLeft':
	        case 'origin':
	            return r.topLeft();

	        case Positions.TOP_RIGHT:
	        case 'topRight':
	            return r.topRight();

	        case Positions.BOTTOM_LEFT:
	        case 'bottomLeft':
	            return r.bottomLeft();

	        case Positions.BOTTOM_RIGHT:
	        case 'bottomRight':
	        case 'corner':
	            return r.bottomRight();

	        // Center
	        case Positions.CENTER:
	            return r.center();

	        // TODO: calc(), percentage etc.
	        default:
	            throw new Error(("Unknown position: " + position));
	    }
	}



	var index = ({
		getRectPoint: getRectPoint,
		wrapWith: wrapWith,
		wrappers: wrappers,
		addClassNamePrefix: addClassNamePrefix,
		removeClassNamePrefix: removeClassNamePrefix,
		parseDOMJSON: parseDOMJSON,
		hashCode: hashCode,
		getByPath: getByPath,
		setByPath: setByPath,
		unsetByPath: unsetByPath,
		flattenObject: flattenObject,
		uuid: uuid,
		guid: guid,
		toKebabCase: toKebabCase,
		normalizeEvent: normalizeEvent,
		normalizeWheel: normalizeWheel,
		cap: cap,
		nextFrame: nextFrame,
		cancelFrame: cancelFrame,
		shapePerimeterConnectionPoint: shapePerimeterConnectionPoint,
		isPercentage: isPercentage,
		parseCssNumeric: parseCssNumeric,
		breakText: breakText,
		sanitizeHTML: sanitizeHTML,
		downloadBlob: downloadBlob,
		downloadDataUri: downloadDataUri,
		dataUriToBlob: dataUriToBlob,
		imageToDataUri: imageToDataUri,
		getElementBBox: getElementBBox,
		sortElements: sortElements,
		setAttributesBySelector: setAttributesBySelector,
		normalizeSides: normalizeSides,
		timing: timing,
		interpolate: interpolate,
		filter: filter,
		format: format,
		template: template,
		toggleFullScreen: toggleFullScreen,
		isBoolean: isBoolean,
		isObject: isObject$1,
		isNumber: isNumber,
		isString: isString,
		mixin: mixin,
		deepMixin: deepMixin,
		supplement: supplement,
		defaults: defaults,
		deepSupplement: deepSupplement,
		defaultsDeep: defaultsDeep,
		assign: assign,
		invoke: invoke,
		invokeProperty: invokeProperty,
		sortedIndex: sortedIndex,
		uniq: uniq,
		clone: clone,
		cloneDeep: cloneDeep,
		isEmpty: isEmpty,
		isEqual: isEqual,
		isFunction: isFunction,
		isPlainObject: isPlainObject,
		toArray: toArray,
		debounce: debounce,
		groupBy: groupBy,
		sortBy: sortBy,
		flattenDeep: flattenDeep,
		without: without,
		difference: difference,
		intersection: intersection$1,
		union: union,
		has: has$2,
		result: result,
		omit: omit,
		pick: pick,
		bindAll: bindAll,
		forIn: forIn,
		camelCase: camelCase,
		uniqueId: uniqueId,
		merge: merge,
		noop: noop,
		cloneCells: cloneCells,
		svg: svg
	});

	function portTransformAttrs(point, angle, opt) {

	    var trans = point.toJSON();

	    trans.angle = angle || 0;

	    return defaults({}, opt, trans);
	}

	function lineLayout(ports, p1, p2, elBBox) {
	    return ports.map(function(port, index, ports) {
	        var p = this.pointAt(((index + 0.5) / ports.length));
	        // `dx`,`dy` per port offset option
	        if (port.dx || port.dy) {
	            p.offset(port.dx || 0, port.dy || 0);
	        }
	        return portTransformAttrs(p.round(), 0, argTransform(elBBox, port));
	    }, line(p1, p2));
	}

	function ellipseLayout(ports, elBBox, startAngle, stepFn) {

	    var center = elBBox.center();
	    var ratio = elBBox.width / elBBox.height;
	    var p1 = elBBox.topMiddle();

	    var ellipse = Ellipse.fromRect(elBBox);

	    return ports.map(function(port, index, ports) {

	        var angle = startAngle + stepFn(index, ports.length);
	        var p2 = p1.clone()
	            .rotate(center, -angle)
	            .scale(ratio, 1, center);

	        var theta = port.compensateRotation ? -ellipse.tangentTheta(p2) : 0;

	        // `dx`,`dy` per port offset option
	        if (port.dx || port.dy) {
	            p2.offset(port.dx || 0, port.dy || 0);
	        }

	        // `dr` delta radius option
	        if (port.dr) {
	            p2.move(center, port.dr);
	        }

	        return portTransformAttrs(p2.round(), theta, argTransform(elBBox, port));
	    });
	}


	function argTransform(bbox, args) {
	    var x = args.x;
	    var y = args.y;
	    var angle = args.angle;
	    if (isPercentage(x)) {
	        x = parseFloat(x) / 100 * bbox.width;
	    } else if (isCalcAttribute(x)) {
	        x = Number(evalCalcAttribute(x, bbox));
	    }
	    if (isPercentage(y)) {
	        y = parseFloat(y) / 100 * bbox.height;
	    } else if (isCalcAttribute(y)) {
	        y = Number(evalCalcAttribute(y, bbox));
	    }
	    return { x: x, y: y, angle: angle };
	}

	// Creates a point stored in arguments
	function argPoint(bbox, args) {
	    var ref = argTransform(bbox, args);
	    var x = ref.x;
	    var y = ref.y;
	    return new Point(x || 0, y || 0);
	}


	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var absolute = function(ports, elBBox) {
	    return ports.map(function (port) {
	        var transformation = argPoint(elBBox, port).round().toJSON();
	        transformation.angle = port.angle || 0;
	        return transformation;
	    });
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var fn = function(ports, elBBox, opt) {
	    return opt.fn(ports, elBBox, opt);
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var line$1 = function(ports, elBBox, opt) {

	    var start = argPoint(elBBox, opt.start || elBBox.origin());
	    var end = argPoint(elBBox, opt.end || elBBox.corner());

	    return lineLayout(ports, start, end, elBBox);
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var left = function(ports, elBBox, opt) {
	    return lineLayout(ports, elBBox.origin(), elBBox.bottomLeft(), elBBox);
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var right = function(ports, elBBox, opt) {
	    return lineLayout(ports, elBBox.topRight(), elBBox.corner(), elBBox);
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var top = function(ports, elBBox, opt) {
	    return lineLayout(ports, elBBox.origin(), elBBox.topRight(), elBBox);
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt opt Group options
	 * @returns {Array<g.Point>}
	 */
	var bottom = function(ports, elBBox, opt) {
	    return lineLayout(ports, elBBox.bottomLeft(), elBBox.corner(), elBBox);
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt Group options
	 * @returns {Array<g.Point>}
	 */
	var ellipseSpread = function(ports, elBBox, opt) {

	    var startAngle = opt.startAngle || 0;
	    var stepAngle = opt.step || 360 / ports.length;

	    return ellipseLayout(ports, elBBox, startAngle, function(index) {
	        return index * stepAngle;
	    });
	};

	/**
	 * @param {Array<Object>} ports
	 * @param {g.Rect} elBBox
	 * @param {Object=} opt Group options
	 * @returns {Array<g.Point>}
	 */
	var ellipse$1 = function(ports, elBBox, opt) {

	    var startAngle = opt.startAngle || 0;
	    var stepAngle = opt.step || 20;

	    return ellipseLayout(ports, elBBox, startAngle, function(index, count) {
	        return (index + 0.5 - count / 2) * stepAngle;
	    });
	};

	var Port = ({
		absolute: absolute,
		fn: fn,
		line: line$1,
		left: left,
		right: right,
		top: top,
		bottom: bottom,
		ellipseSpread: ellipseSpread,
		ellipse: ellipse$1
	});

	function labelAttributes(opt1, opt2) {

	    return defaultsDeep({}, opt1, opt2, {
	        x: 0,
	        y: 0,
	        angle: 0,
	        attrs: {}
	    });
	}

	function outsideLayout(portPosition, elBBox, autoOrient, opt) {

	    opt = defaults({}, opt, { offset: 15 });
	    var angle = elBBox.center().theta(portPosition);
	    var x = getBBoxAngles(elBBox);

	    var tx, ty, y, textAnchor;
	    var offset = opt.offset;
	    var orientAngle = 0;

	    if (angle < x[1] || angle > x[2]) {
	        y = '.3em';
	        tx = offset;
	        ty = 0;
	        textAnchor = 'start';
	    } else if (angle < x[0]) {
	        tx = 0;
	        ty = -offset;
	        if (autoOrient) {
	            orientAngle = -90;
	            textAnchor = 'start';
	            y = '.3em';
	        } else {
	            textAnchor = 'middle';
	            y = '0';
	        }
	    } else if (angle < x[3]) {
	        y = '.3em';
	        tx = -offset;
	        ty = 0;
	        textAnchor = 'end';
	    } else {
	        tx = 0;
	        ty = offset;
	        if (autoOrient) {
	            orientAngle = 90;
	            textAnchor = 'start';
	            y = '.3em';
	        } else {
	            textAnchor = 'middle';
	            y = '.6em';
	        }
	    }

	    var round = Math.round;
	    return labelAttributes({
	        x: round(tx),
	        y: round(ty),
	        angle: orientAngle,
	        attrs: { labelText: { y: y, textAnchor: textAnchor }}
	    });
	}

	function getBBoxAngles(elBBox) {

	    var center = elBBox.center();

	    var tl = center.theta(elBBox.origin());
	    var bl = center.theta(elBBox.bottomLeft());
	    var br = center.theta(elBBox.corner());
	    var tr = center.theta(elBBox.topRight());

	    return [tl, tr, br, bl];
	}

	function insideLayout(portPosition, elBBox, autoOrient, opt) {

	    var angle = elBBox.center().theta(portPosition);
	    opt = defaults({}, opt, { offset: 15 });

	    var tx, ty, y, textAnchor;
	    var offset = opt.offset;
	    var orientAngle = 0;

	    var bBoxAngles = getBBoxAngles(elBBox);

	    if (angle < bBoxAngles[1] || angle > bBoxAngles[2]) {
	        y = '.3em';
	        tx = -offset;
	        ty = 0;
	        textAnchor = 'end';
	    } else if (angle < bBoxAngles[0]) {
	        tx = 0;
	        ty = offset;
	        if (autoOrient) {
	            orientAngle = 90;
	            textAnchor = 'start';
	            y = '.3em';
	        } else {
	            textAnchor = 'middle';
	            y = '.6em';
	        }
	    } else if (angle < bBoxAngles[3]) {
	        y = '.3em';
	        tx = offset;
	        ty = 0;
	        textAnchor = 'start';
	    } else {
	        tx = 0;
	        ty = -offset;
	        if (autoOrient) {
	            orientAngle = -90;
	            textAnchor = 'start';
	            y = '.3em';
	        } else {
	            textAnchor = 'middle';
	            y = '0';
	        }
	    }

	    var round = Math.round;
	    return labelAttributes({
	        x: round(tx),
	        y: round(ty),
	        angle: orientAngle,
	        attrs: { labelText: { y: y, textAnchor: textAnchor }}
	    });
	}

	function radialLayout(portCenterOffset, autoOrient, opt) {

	    opt = defaults({}, opt, { offset: 20 });

	    var origin = point(0, 0);
	    var angle = -portCenterOffset.theta(origin);
	    var orientAngle = angle;
	    var offset = portCenterOffset.clone()
	        .move(origin, opt.offset)
	        .difference(portCenterOffset)
	        .round();

	    var y = '.3em';
	    var textAnchor;

	    if ((angle + 90) % 180 === 0) {
	        textAnchor = autoOrient ? 'end' : 'middle';
	        if (!autoOrient && angle === -270) {
	            y = '0em';
	        }
	    } else if (angle > -270 && angle < -90) {
	        textAnchor = 'start';
	        orientAngle = angle - 180;
	    } else {
	        textAnchor = 'end';
	    }

	    var round = Math.round;
	    return labelAttributes({
	        x: round(offset.x),
	        y: round(offset.y),
	        angle: autoOrient ? orientAngle : 0,
	        attrs: {
	            labelText: {
	                y: y,
	                textAnchor: textAnchor
	            }
	        }
	    });
	}

	var manual = function(_portPosition, _elBBox, opt) {
	    return labelAttributes(opt);
	};

	var left$1 = function(portPosition, elBBox, opt) {
	    return labelAttributes(opt, {
	        x: -15,
	        attrs: { labelText: { y: '.3em', textAnchor: 'end' }},
	    });
	};

	var right$1 = function(portPosition, elBBox, opt) {
	    return labelAttributes(opt, {
	        x: 15,
	        attrs: { labelText: { y: '.3em', textAnchor: 'start' }},
	    });
	};

	var top$1 = function(portPosition, elBBox, opt) {
	    return labelAttributes(opt, {
	        y: -15,
	        attrs: { labelText: { y: '0', textAnchor: 'middle' }},
	    });
	};

	var bottom$1 = function(portPosition, elBBox, opt) {
	    return labelAttributes(opt, {
	        y: 15,
	        attrs: { labelText: { y: '.6em', textAnchor: 'middle' }},
	    });
	};

	var outsideOriented = function(portPosition, elBBox, opt) {
	    return outsideLayout(portPosition, elBBox, true, opt);
	};

	var outside = function(portPosition, elBBox, opt) {
	    return outsideLayout(portPosition, elBBox, false, opt);
	};

	var insideOriented = function(portPosition, elBBox, opt) {
	    return insideLayout(portPosition, elBBox, true, opt);
	};

	var inside = function(portPosition, elBBox, opt) {
	    return insideLayout(portPosition, elBBox, false, opt);
	};

	var radial = function(portPosition, elBBox, opt) {
	    return radialLayout(portPosition.difference(elBBox.center()), false, opt);
	};

	var radialOriented = function(portPosition, elBBox, opt) {
	    return radialLayout(portPosition.difference(elBBox.center()), true, opt);
	};

	var PortLabel = ({
		manual: manual,
		left: left$1,
		right: right$1,
		top: top$1,
		bottom: bottom$1,
		outsideOriented: outsideOriented,
		outside: outside,
		insideOriented: insideOriented,
		inside: inside,
		radial: radial,
		radialOriented: radialOriented
	});

	// Link base model.
	// --------------------------

	var Link = Cell.extend({

	    // The default markup for links.
	    markup: [
	        '<path class="connection" stroke="black" d="M 0 0 0 0"/>',
	        '<path class="marker-source" fill="black" stroke="black" d="M 0 0 0 0"/>',
	        '<path class="marker-target" fill="black" stroke="black" d="M 0 0 0 0"/>',
	        '<path class="connection-wrap" d="M 0 0 0 0"/>',
	        '<g class="labels"/>',
	        '<g class="marker-vertices"/>',
	        '<g class="marker-arrowheads"/>',
	        '<g class="link-tools"/>'
	    ].join(''),

	    toolMarkup: [
	        '<g class="link-tool">',
	        '<g class="tool-remove" event="remove">',
	        '<circle r="11" />',
	        '<path transform="scale(.8) translate(-16, -16)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z" />',
	        '<title>Remove link.</title>',
	        '</g>',
	        '<g class="tool-options" event="link:options">',
	        '<circle r="11" transform="translate(25)"/>',
	        '<path fill="white" transform="scale(.55) translate(29, -16)" d="M31.229,17.736c0.064-0.571,0.104-1.148,0.104-1.736s-0.04-1.166-0.104-1.737l-4.377-1.557c-0.218-0.716-0.504-1.401-0.851-2.05l1.993-4.192c-0.725-0.91-1.549-1.734-2.458-2.459l-4.193,1.994c-0.647-0.347-1.334-0.632-2.049-0.849l-1.558-4.378C17.165,0.708,16.588,0.667,16,0.667s-1.166,0.041-1.737,0.105L12.707,5.15c-0.716,0.217-1.401,0.502-2.05,0.849L6.464,4.005C5.554,4.73,4.73,5.554,4.005,6.464l1.994,4.192c-0.347,0.648-0.632,1.334-0.849,2.05l-4.378,1.557C0.708,14.834,0.667,15.412,0.667,16s0.041,1.165,0.105,1.736l4.378,1.558c0.217,0.715,0.502,1.401,0.849,2.049l-1.994,4.193c0.725,0.909,1.549,1.733,2.459,2.458l4.192-1.993c0.648,0.347,1.334,0.633,2.05,0.851l1.557,4.377c0.571,0.064,1.148,0.104,1.737,0.104c0.588,0,1.165-0.04,1.736-0.104l1.558-4.377c0.715-0.218,1.399-0.504,2.049-0.851l4.193,1.993c0.909-0.725,1.733-1.549,2.458-2.458l-1.993-4.193c0.347-0.647,0.633-1.334,0.851-2.049L31.229,17.736zM16,20.871c-2.69,0-4.872-2.182-4.872-4.871c0-2.69,2.182-4.872,4.872-4.872c2.689,0,4.871,2.182,4.871,4.872C20.871,18.689,18.689,20.871,16,20.871z"/>',
	        '<title>Link options.</title>',
	        '</g>',
	        '</g>'
	    ].join(''),

	    doubleToolMarkup: undefined,

	    // The default markup for showing/removing vertices. These elements are the children of the .marker-vertices element (see `this.markup`).
	    // Only .marker-vertex and .marker-vertex-remove element have special meaning. The former is used for
	    // dragging vertices (changing their position). The latter is used for removing vertices.
	    vertexMarkup: [
	        '<g class="marker-vertex-group" transform="translate(<%= x %>, <%= y %>)">',
	        '<circle class="marker-vertex" idx="<%= idx %>" r="10" />',
	        '<path class="marker-vertex-remove-area" idx="<%= idx %>" d="M16,5.333c-7.732,0-14,4.701-14,10.5c0,1.982,0.741,3.833,2.016,5.414L2,25.667l5.613-1.441c2.339,1.317,5.237,2.107,8.387,2.107c7.732,0,14-4.701,14-10.5C30,10.034,23.732,5.333,16,5.333z" transform="translate(5, -33)"/>',
	        '<path class="marker-vertex-remove" idx="<%= idx %>" transform="scale(.8) translate(9.5, -37)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z">',
	        '<title>Remove vertex.</title>',
	        '</path>',
	        '</g>'
	    ].join(''),

	    arrowheadMarkup: [
	        '<g class="marker-arrowhead-group marker-arrowhead-group-<%= end %>">',
	        '<path class="marker-arrowhead" end="<%= end %>" d="M 26 0 L 0 13 L 26 26 z" />',
	        '</g>'
	    ].join(''),

	    // may be overwritten by user to change default label (its markup, size, attrs, position)
	    defaultLabel: undefined,

	    // deprecated
	    // may be overwritten by user to change default label markup
	    // lower priority than defaultLabel.markup
	    labelMarkup: undefined,

	    // private
	    _builtins: {
	        defaultLabel: {
	            // builtin default markup:
	            // used if neither defaultLabel.markup
	            // nor label.markup is set
	            markup: [
	                {
	                    tagName: 'rect',
	                    selector: 'rect' // faster than tagName CSS selector
	                }, {
	                    tagName: 'text',
	                    selector: 'text' // faster than tagName CSS selector
	                }
	            ],
	            // builtin default attributes:
	            // applied only if builtin default markup is used
	            attrs: {
	                text: {
	                    fill: '#000000',
	                    fontSize: 14,
	                    textAnchor: 'middle',
	                    yAlignment: 'middle',
	                    pointerEvents: 'none'
	                },
	                rect: {
	                    ref: 'text',
	                    fill: '#ffffff',
	                    rx: 3,
	                    ry: 3,
	                    refWidth: 1,
	                    refHeight: 1,
	                    refX: 0,
	                    refY: 0
	                }
	            },
	            // builtin default position:
	            // used if neither defaultLabel.position
	            // nor label.position is set
	            position: {
	                distance: 0.5
	            }
	        }
	    },

	    defaults: {
	        type: 'link',
	        source: {},
	        target: {}
	    },

	    isLink: function() {

	        return true;
	    },

	    disconnect: function(opt) {

	        return this.set({
	            source: { x: 0, y: 0 },
	            target: { x: 0, y: 0 }
	        }, opt);
	    },

	    source: function(source, args, opt) {

	        // getter
	        if (source === undefined) {
	            return clone(this.get('source'));
	        }

	        // setter
	        var setSource;
	        var setOpt;

	        // `source` is a cell
	        // take only its `id` and combine with `args`
	        var isCellProvided = source instanceof Cell;
	        if (isCellProvided) { // three arguments
	            setSource = clone(args) || {};
	            setSource.id = source.id;
	            setOpt = opt;
	            return this.set('source', setSource, setOpt);
	        }

	        // `source` is a point-like object
	        // for example, a g.Point
	        // take only its `x` and `y` and combine with `args`
	        var isPointProvided = !isPlainObject(source);
	        if (isPointProvided) { // three arguments
	            setSource = clone(args) || {};
	            setSource.x = source.x;
	            setSource.y = source.y;
	            setOpt = opt;
	            return this.set('source', setSource, setOpt);
	        }

	        // `source` is an object
	        // no checking
	        // two arguments
	        setSource = source;
	        setOpt = args;
	        return this.set('source', setSource, setOpt);
	    },

	    target: function(target, args, opt) {

	        // getter
	        if (target === undefined) {
	            return clone(this.get('target'));
	        }

	        // setter
	        var setTarget;
	        var setOpt;

	        // `target` is a cell
	        // take only its `id` argument and combine with `args`
	        var isCellProvided = target instanceof Cell;
	        if (isCellProvided) { // three arguments
	            setTarget = clone(args) || {};
	            setTarget.id = target.id;
	            setOpt = opt;
	            return this.set('target', setTarget, setOpt);
	        }

	        // `target` is a point-like object
	        // for example, a g.Point
	        // take only its `x` and `y` and combine with `args`
	        var isPointProvided = !isPlainObject(target);
	        if (isPointProvided) { // three arguments
	            setTarget = clone(args) || {};
	            setTarget.x = target.x;
	            setTarget.y = target.y;
	            setOpt = opt;
	            return this.set('target', setTarget, setOpt);
	        }

	        // `target` is an object
	        // no checking
	        // two arguments
	        setTarget = target;
	        setOpt = args;
	        return this.set('target', setTarget, setOpt);
	    },

	    router: function(name, args, opt) {

	        // getter
	        if (name === undefined) {
	            var router = this.get('router');
	            if (!router) {
	                if (this.get('manhattan')) { return { name: 'orthogonal' }; } // backwards compatibility
	                return null;
	            }
	            if (typeof router === 'object') { return clone(router); }
	            return router; // e.g. a function
	        }

	        // setter
	        var isRouterProvided = ((typeof name === 'object') || (typeof name === 'function'));
	        var localRouter = isRouterProvided ? name : { name: name, args: args };
	        var localOpt = isRouterProvided ? args : opt;

	        return this.set('router', localRouter, localOpt);
	    },

	    connector: function(name, args, opt) {

	        // getter
	        if (name === undefined) {
	            var connector = this.get('connector');
	            if (!connector) {
	                if (this.get('smooth')) { return { name: 'smooth' }; } // backwards compatibility
	                return null;
	            }
	            if (typeof connector === 'object') { return clone(connector); }
	            return connector; // e.g. a function
	        }

	        // setter
	        var isConnectorProvided = ((typeof name === 'object' || typeof name === 'function'));
	        var localConnector = isConnectorProvided ? name : { name: name, args: args };
	        var localOpt = isConnectorProvided ? args : opt;

	        return this.set('connector', localConnector, localOpt);
	    },

	    // Labels API

	    // A convenient way to set labels. Currently set values will be mixined with `value` if used as a setter.
	    label: function(idx, label, opt) {

	        var labels = this.labels();

	        idx = (isFinite(idx) && idx !== null) ? (idx | 0) : 0;
	        if (idx < 0) { idx = labels.length + idx; }

	        // getter
	        if (arguments.length <= 1) { return this.prop(['labels', idx]); }
	        // setter
	        return this.prop(['labels', idx], label, opt);
	    },

	    labels: function(labels, opt) {

	        // getter
	        if (arguments.length === 0) {
	            labels = this.get('labels');
	            if (!Array.isArray(labels)) { return []; }
	            return labels.slice();
	        }
	        // setter
	        if (!Array.isArray(labels)) { labels = []; }
	        return this.set('labels', labels, opt);
	    },

	    hasLabels: function() {
	        var ref = this.attributes;
	        var labels = ref.labels;
	        return Array.isArray(labels) && labels.length > 0;
	    },

	    insertLabel: function(idx, label, opt) {

	        if (!label) { throw new Error('dia.Link: no label provided'); }

	        var labels = this.labels();
	        var n = labels.length;
	        idx = (isFinite(idx) && idx !== null) ? (idx | 0) : n;
	        if (idx < 0) { idx = n + idx + 1; }

	        labels.splice(idx, 0, label);
	        return this.labels(labels, opt);
	    },

	    // convenience function
	    // add label to end of labels array
	    appendLabel: function(label, opt) {

	        return this.insertLabel(-1, label, opt);
	    },

	    removeLabel: function(idx, opt) {

	        var labels = this.labels();
	        idx = (isFinite(idx) && idx !== null) ? (idx | 0) : -1;

	        labels.splice(idx, 1);
	        return this.labels(labels, opt);
	    },

	    // Vertices API

	    vertex: function(idx, vertex, opt) {

	        var vertices = this.vertices();

	        idx = (isFinite(idx) && idx !== null) ? (idx | 0) : 0;
	        if (idx < 0) { idx = vertices.length + idx; }

	        // getter
	        if (arguments.length <= 1) { return this.prop(['vertices', idx]); }

	        // setter
	        var setVertex = this._normalizeVertex(vertex);
	        return this.prop(['vertices', idx], setVertex, opt);
	    },

	    vertices: function(vertices, opt) {

	        // getter
	        if (arguments.length === 0) {
	            vertices = this.get('vertices');
	            if (!Array.isArray(vertices)) { return []; }
	            return vertices.slice();
	        }

	        // setter
	        if (!Array.isArray(vertices)) { vertices = []; }
	        var setVertices = [];
	        for (var i = 0; i < vertices.length; i++) {
	            var vertex = vertices[i];
	            var setVertex = this._normalizeVertex(vertex);
	            setVertices.push(setVertex);
	        }
	        return this.set('vertices', setVertices, opt);
	    },

	    insertVertex: function(idx, vertex, opt) {

	        if (!vertex) { throw new Error('dia.Link: no vertex provided'); }

	        var vertices = this.vertices();
	        var n = vertices.length;
	        idx = (isFinite(idx) && idx !== null) ? (idx | 0) : n;
	        if (idx < 0) { idx = n + idx + 1; }

	        var setVertex = this._normalizeVertex(vertex);
	        vertices.splice(idx, 0, setVertex);
	        return this.vertices(vertices, opt);
	    },

	    removeVertex: function(idx, opt) {

	        var vertices = this.vertices();
	        idx = (isFinite(idx) && idx !== null) ? (idx | 0) : -1;

	        vertices.splice(idx, 1);
	        return this.vertices(vertices, opt);
	    },

	    _normalizeVertex: function(vertex) {

	        // is vertex a point-like object?
	        // for example, a g.Point
	        var isPointProvided = !isPlainObject(vertex);
	        if (isPointProvided) { return { x: vertex.x, y: vertex.y }; }

	        // else: return vertex unchanged
	        return vertex;
	    },

	    // Transformations

	    translate: function(tx, ty, opt) {

	        // enrich the option object
	        opt = opt || {};
	        opt.translateBy = opt.translateBy || this.id;
	        opt.tx = tx;
	        opt.ty = ty;

	        return this.applyToPoints(function(p) {
	            return { x: (p.x || 0) + tx, y: (p.y || 0) + ty };
	        }, opt);
	    },

	    scale: function(sx, sy, origin, opt) {

	        return this.applyToPoints(function(p) {
	            return Point(p).scale(sx, sy, origin).toJSON();
	        }, opt);
	    },

	    applyToPoints: function(fn, opt) {

	        if (!isFunction(fn)) {
	            throw new TypeError('dia.Link: applyToPoints expects its first parameter to be a function.');
	        }

	        var attrs = {};

	        var ref = this.attributes;
	        var source = ref.source;
	        var target = ref.target;
	        if (!source.id) {
	            attrs.source = fn(source);
	        }
	        if (!target.id) {
	            attrs.target = fn(target);
	        }

	        var vertices = this.vertices();
	        if (vertices.length > 0) {
	            attrs.vertices = vertices.map(fn);
	        }

	        return this.set(attrs, opt);
	    },

	    getSourcePoint: function() {
	        var sourceCell = this.getSourceCell();
	        if (!sourceCell) { return new Point(this.source()); }
	        return sourceCell.getPointFromConnectedLink(this, 'source');
	    },

	    getTargetPoint: function() {
	        var targetCell = this.getTargetCell();
	        if (!targetCell) { return new Point(this.target()); }
	        return targetCell.getPointFromConnectedLink(this, 'target');
	    },

	    getPointFromConnectedLink: function(/* link, endType */) {
	        return this.getPolyline().pointAt(0.5);
	    },

	    getPolyline: function() {
	        var points = [
	            this.getSourcePoint() ].concat( this.vertices().map(Point),
	            [this.getTargetPoint()]
	        );
	        return new Polyline(points);
	    },

	    getBBox: function() {
	        return this.getPolyline().bbox();
	    },

	    reparent: function(opt) {

	        var newParent;

	        if (this.graph) {

	            var source = this.getSourceElement();
	            var target = this.getTargetElement();
	            var prevParent = this.getParentCell();

	            if (source && target) {
	                if (source === target || source.isEmbeddedIn(target)) {
	                    newParent = target;
	                } else if (target.isEmbeddedIn(source)) {
	                    newParent = source;
	                } else {
	                    newParent = this.graph.getCommonAncestor(source, target);
	                }
	            }

	            if (prevParent && (!newParent || newParent.id !== prevParent.id)) {
	                // Unembed the link if source and target has no common ancestor
	                // or common ancestor changed
	                prevParent.unembed(this, opt);
	            }

	            if (newParent) {
	                newParent.embed(this, opt);
	            }
	        }

	        return newParent;
	    },

	    hasLoop: function(opt) {

	        opt = opt || {};

	        var ref = this.attributes;
	        var source = ref.source;
	        var target = ref.target;
	        var sourceId = source.id;
	        var targetId = target.id;

	        if (!sourceId || !targetId) {
	            // Link "pinned" to the paper does not have a loop.
	            return false;
	        }

	        var loop = sourceId === targetId;

	        // Note that there in the deep mode a link can have a loop,
	        // even if it connects only a parent and its embed.
	        // A loop "target equals source" is valid in both shallow and deep mode.
	        if (!loop && opt.deep && this.graph) {

	            var sourceElement = this.getSourceCell();
	            var targetElement = this.getTargetCell();

	            loop = sourceElement.isEmbeddedIn(targetElement) || targetElement.isEmbeddedIn(sourceElement);
	        }

	        return loop;
	    },

	    // unlike source(), this method returns null if source is a point
	    getSourceCell: function() {

	        var ref = this;
	        var graph = ref.graph;
	        var attributes = ref.attributes;
	        var source = attributes.source;
	        return (source && source.id && graph && graph.getCell(source.id)) || null;
	    },

	    getSourceElement: function() {
	        var cell = this;
	        var visited = {};
	        do {
	            if (visited[cell.id]) { return null; }
	            visited[cell.id] = true;
	            cell = cell.getSourceCell();
	        } while (cell && cell.isLink());
	        return cell;
	    },

	    // unlike target(), this method returns null if target is a point
	    getTargetCell: function() {

	        var ref = this;
	        var graph = ref.graph;
	        var attributes = ref.attributes;
	        var target = attributes.target;
	        return (target && target.id && graph && graph.getCell(target.id)) || null;
	    },

	    getTargetElement: function() {
	        var cell = this;
	        var visited = {};
	        do {
	            if (visited[cell.id]) { return null; }
	            visited[cell.id] = true;
	            cell = cell.getTargetCell();
	        } while (cell && cell.isLink());
	        return cell;
	    },

	    // Returns the common ancestor for the source element,
	    // target element and the link itself.
	    getRelationshipAncestor: function() {

	        var connectionAncestor;

	        if (this.graph) {

	            var cells = [
	                this,
	                this.getSourceElement(), // null if source is a point
	                this.getTargetElement() // null if target is a point
	            ].filter(function(item) {
	                return !!item;
	            });

	            connectionAncestor = this.graph.getCommonAncestor.apply(this.graph, cells);
	        }

	        return connectionAncestor || null;
	    },

	    // Is source, target and the link itself embedded in a given cell?
	    isRelationshipEmbeddedIn: function(cell) {

	        var cellId = (isString(cell) || isNumber(cell)) ? cell : cell.id;
	        var ancestor = this.getRelationshipAncestor();

	        return !!ancestor && (ancestor.id === cellId || ancestor.isEmbeddedIn(cellId));
	    },

	    // Get resolved default label.
	    _getDefaultLabel: function() {

	        var defaultLabel = this.get('defaultLabel') || this.defaultLabel || {};

	        var label = {};
	        label.markup = defaultLabel.markup || this.get('labelMarkup') || this.labelMarkup;
	        label.position = defaultLabel.position;
	        label.attrs = defaultLabel.attrs;
	        label.size = defaultLabel.size;

	        return label;
	    }
	}, {

	    endsEqual: function(a, b) {

	        var portsEqual = a.port === b.port || !a.port && !b.port;
	        return a.id === b.id && portsEqual;
	    }
	});

	var PortData = function(data) {

	    var clonedData = cloneDeep(data) || {};
	    this.ports = [];
	    this.groups = {};
	    this.portLayoutNamespace = Port;
	    this.portLabelLayoutNamespace = PortLabel;

	    this._init(clonedData);
	};

	PortData.prototype = {

	    getPorts: function() {
	        return this.ports;
	    },

	    getGroup: function(name) {
	        return this.groups[name] || {};
	    },

	    getPortsByGroup: function(groupName) {

	        return this.ports.filter(function(port) {
	            return port.group === groupName;
	        });
	    },

	    getGroupPortsMetrics: function(groupName, elBBox) {

	        var group = this.getGroup(groupName);
	        var ports = this.getPortsByGroup(groupName);

	        var groupPosition = group.position || {};
	        var groupPositionName = groupPosition.name;
	        var namespace = this.portLayoutNamespace;
	        if (!namespace[groupPositionName]) {
	            groupPositionName = 'left';
	        }

	        var groupArgs = groupPosition.args || {};
	        var portsArgs = ports.map(function(port) {
	            return port && port.position && port.position.args;
	        });
	        var groupPortTransformations = namespace[groupPositionName](portsArgs, elBBox, groupArgs);

	        var accumulator = {
	            ports: ports,
	            result: []
	        };

	        toArray(groupPortTransformations).reduce(function(res, portTransformation, index) {
	            var port = res.ports[index];
	            res.result.push({
	                portId: port.id,
	                portTransformation: portTransformation,
	                labelTransformation: this._getPortLabelLayout(port, Point(portTransformation), elBBox),
	                portAttrs: port.attrs,
	                portSize: port.size,
	                labelSize: port.label.size
	            });
	            return res;
	        }.bind(this), accumulator);

	        return accumulator.result;
	    },

	    _getPortLabelLayout: function(port, portPosition, elBBox) {

	        var namespace = this.portLabelLayoutNamespace;
	        var labelPosition = port.label.position.name || 'left';

	        if (namespace[labelPosition]) {
	            return namespace[labelPosition](portPosition, elBBox, port.label.position.args);
	        }

	        return null;
	    },

	    _init: function(data) {

	        // prepare groups
	        if (isObject$1(data.groups)) {
	            var groups = Object.keys(data.groups);
	            for (var i = 0, n = groups.length; i < n; i++) {
	                var key = groups[i];
	                this.groups[key] = this._evaluateGroup(data.groups[key]);
	            }
	        }

	        // prepare ports
	        var ports = toArray(data.items);
	        for (var j = 0, m = ports.length; j < m; j++) {
	            this.ports.push(this._evaluatePort(ports[j]));
	        }
	    },

	    _evaluateGroup: function(group) {

	        return merge(group, {
	            position: this._getPosition(group.position, true),
	            label: this._getLabel(group, true)
	        });
	    },

	    _evaluatePort: function(port) {

	        var evaluated = assign({}, port);

	        var group = this.getGroup(port.group);

	        evaluated.markup = evaluated.markup || group.markup;
	        evaluated.attrs = merge({}, group.attrs, evaluated.attrs);
	        evaluated.position = this._createPositionNode(group, evaluated);
	        evaluated.label = merge({}, group.label, this._getLabel(evaluated));
	        evaluated.z = this._getZIndex(group, evaluated);
	        evaluated.size = assign({}, group.size, evaluated.size);

	        return evaluated;
	    },

	    _getZIndex: function(group, port) {

	        if (isNumber(port.z)) {
	            return port.z;
	        }
	        if (isNumber(group.z) || group.z === 'auto') {
	            return group.z;
	        }
	        return 'auto';
	    },

	    _createPositionNode: function(group, port) {

	        return merge({
	            name: 'left',
	            args: {}
	        }, group.position, { args: port.args });
	    },

	    _getPosition: function(position, setDefault) {

	        var args = {};
	        var positionName;

	        if (isFunction(position)) {
	            positionName = 'fn';
	            args.fn = position;
	        } else if (isString(position)) {
	            positionName = position;
	        } else if (position === undefined) {
	            positionName = setDefault ? 'left' : null;
	        } else if (Array.isArray(position)) {
	            positionName = 'absolute';
	            args.x = position[0];
	            args.y = position[1];
	        } else if (isObject$1(position)) {
	            positionName = position.name;
	            assign(args, position.args);
	        }

	        var result = { args: args };

	        if (positionName) {
	            result.name = positionName;
	        }
	        return result;
	    },

	    _getLabel: function(item, setDefaults) {

	        var label = item.label || {};

	        var ret = label;
	        ret.position = this._getPosition(label.position, setDefaults);

	        return ret;
	    }
	};

	var elementPortPrototype = {

	    _initializePorts: function() {

	        this._createPortData();
	        this.on('change:ports', function() {

	            this._processRemovedPort();
	            this._createPortData();
	        }, this);
	    },

	    /**
	     * remove links tied wiht just removed element
	     * @private
	     */
	    _processRemovedPort: function() {

	        var current = this.get('ports') || {};
	        var currentItemsMap = {};

	        toArray(current.items).forEach(function(item) {
	            currentItemsMap[item.id] = true;
	        });

	        var previous = this.previous('ports') || {};
	        var removed = {};

	        toArray(previous.items).forEach(function(item) {
	            if (!currentItemsMap[item.id]) {
	                removed[item.id] = true;
	            }
	        });

	        var graph = this.graph;
	        if (graph && !isEmpty(removed)) {

	            var inboundLinks = graph.getConnectedLinks(this, { inbound: true });
	            inboundLinks.forEach(function(link) {

	                if (removed[link.get('target').port]) { link.remove(); }
	            });

	            var outboundLinks = graph.getConnectedLinks(this, { outbound: true });
	            outboundLinks.forEach(function(link) {

	                if (removed[link.get('source').port]) { link.remove(); }
	            });
	        }
	    },

	    /**
	     * @returns {boolean}
	     */
	    hasPorts: function() {

	        var ports = this.prop('ports/items');
	        return Array.isArray(ports) && ports.length > 0;
	    },

	    /**
	     * @param {string} id
	     * @returns {boolean}
	     */
	    hasPort: function(id) {

	        return this.getPortIndex(id) !== -1;
	    },

	    /**
	     * @returns {Array<object>}
	     */
	    getPorts: function() {

	        return cloneDeep(this.prop('ports/items')) || [];
	    },

	    /**
	     * @returns {Array<object>}
	     */
	    getGroupPorts: function(groupName) {
	        var groupPorts = toArray(this.prop(['ports','items'])).filter(function (port) { return port.group === groupName; });
	        return cloneDeep(groupPorts);
	    },

	    /**
	     * @param {string} id
	     * @returns {object}
	     */
	    getPort: function(id) {

	        return cloneDeep(toArray(this.prop('ports/items')).find(function(port) {
	            return port.id && port.id === id;
	        }));
	    },

	    /**
	     * @param {string} groupName
	     * @returns {Object<portId, {x: number, y: number, angle: number}>}
	     */
	    getPortsPositions: function(groupName) {

	        var portsMetrics = this._portSettingsData.getGroupPortsMetrics(groupName, Rect(this.size()));

	        return portsMetrics.reduce(function(positions, metrics) {
	            var transformation = metrics.portTransformation;
	            positions[metrics.portId] = {
	                x: transformation.x,
	                y: transformation.y,
	                angle: transformation.angle
	            };
	            return positions;
	        }, {});
	    },

	    /**
	     * @param {string|Port} port port id or port
	     * @returns {number} port index
	     */
	    getPortIndex: function(port) {

	        var id = isObject$1(port) ? port.id : port;

	        if (!this._isValidPortId(id)) {
	            return -1;
	        }

	        return toArray(this.prop('ports/items')).findIndex(function(item) {
	            return item.id === id;
	        });
	    },

	    /**
	     * @param {object} port
	     * @param {object} [opt]
	     * @returns {joint.dia.Element}
	     */
	    addPort: function(port, opt) {

	        if (!isObject$1(port) || Array.isArray(port)) {
	            throw new Error('Element: addPort requires an object.');
	        }

	        var ports = assign([], this.prop('ports/items'));
	        ports.push(port);
	        this.prop('ports/items', ports, opt);

	        return this;
	    },

	    /**
	     * @param {string|Port|number} before
	     * @param {object} port
	     * @param {object} [opt]
	     * @returns {joint.dia.Element}
	     */
	    insertPort: function(before, port, opt) {
	        var index$1 = (typeof before === 'number') ? before : this.getPortIndex(before);

	        if (!isObject$1(port) || Array.isArray(port)) {
	            throw new Error('dia.Element: insertPort requires an object.');
	        }

	        var ports = assign([], this.prop('ports/items'));
	        ports.splice(index$1, 0, port);
	        this.prop('ports/items', ports, opt);

	        return this;
	    },

	    /**
	     * @param {string} portId
	     * @param {string|object=} path
	     * @param {*=} value
	     * @param {object=} opt
	     * @returns {joint.dia.Element}
	     */
	    portProp: function(portId, path, value, opt) {

	        var index$1 = this.getPortIndex(portId);

	        if (index$1 === -1) {
	            throw new Error('Element: unable to find port with id ' + portId);
	        }

	        var args = Array.prototype.slice.call(arguments, 1);
	        if (Array.isArray(path)) {
	            args[0] = ['ports', 'items', index$1].concat(path);
	        } else if (isString(path)) {

	            // Get/set an attribute by a special path syntax that delimits
	            // nested objects by the colon character.
	            args[0] = ['ports/items/', index$1, '/', path].join('');

	        } else {

	            args = ['ports/items/' + index$1];
	            if (isPlainObject(path)) {
	                args.push(path);
	                args.push(value);
	            }
	        }

	        return this.prop.apply(this, args);
	    },

	    _validatePorts: function() {

	        var portsAttr = this.get('ports') || {};

	        var errorMessages = [];
	        portsAttr = portsAttr || {};
	        var ports = toArray(portsAttr.items);

	        ports.forEach(function(p) {

	            if (typeof p !== 'object') {
	                errorMessages.push('Element: invalid port ', p);
	            }

	            if (!this._isValidPortId(p.id)) {
	                p.id = this.generatePortId();
	            }
	        }, this);

	        if (uniq(ports, 'id').length !== ports.length) {
	            errorMessages.push('Element: found id duplicities in ports.');
	        }

	        return errorMessages;
	    },

	    generatePortId: function() {
	        return this.generateId();
	    },

	    /**
	     * @param {string} id port id
	     * @returns {boolean}
	     * @private
	     */
	    _isValidPortId: function(id) {

	        return id !== null && id !== undefined && !isObject$1(id);
	    },

	    addPorts: function(ports, opt) {

	        if (ports.length) {
	            this.prop('ports/items', assign([], this.prop('ports/items')).concat(ports), opt);
	        }

	        return this;
	    },

	    removePort: function(port, opt) {
	        var options = opt || {};
	        var index$1 = this.getPortIndex(port);
	        if (index$1 !== -1) {
	            var ports = assign([], this.prop(['ports', 'items']));
	            ports.splice(index$1, 1);
	            options.rewrite = true;
	            this.startBatch('port-remove');
	            this.prop(['ports', 'items'], ports, options);
	            this.stopBatch('port-remove');
	        }
	        return this;
	    },

	    removePorts: function(portsForRemoval, opt) {
	        var options, newPorts;
	        if (Array.isArray(portsForRemoval)) {
	            options = opt || {};
	            if (portsForRemoval.length === 0) { return this.this; }
	            var currentPorts = assign([], this.prop(['ports', 'items']));
	            newPorts = currentPorts.filter(function(cp) {
	                return !portsForRemoval.some(function(rp) {
	                    var rpId = isObject$1(rp) ? rp.id : rp;
	                    return cp.id === rpId;
	                });
	            });
	        } else {
	            options = portsForRemoval || {};
	            newPorts = [];
	        }
	        this.startBatch('port-remove');
	        options.rewrite = true;
	        this.prop(['ports', 'items'], newPorts, options);
	        this.stopBatch('port-remove');
	        return this;
	    },

	    /**
	     * @private
	     */
	    _createPortData: function() {

	        var err = this._validatePorts();

	        if (err.length > 0) {
	            this.set('ports', this.previous('ports'));
	            throw new Error(err.join(' '));
	        }

	        var prevPortData;

	        if (this._portSettingsData) {

	            prevPortData = this._portSettingsData.getPorts();
	        }

	        this._portSettingsData = new PortData(this.get('ports'));

	        var curPortData = this._portSettingsData.getPorts();

	        if (prevPortData) {

	            var added = curPortData.filter(function(item) {
	                if (!prevPortData.find(function(prevPort) {
	                    return prevPort.id === item.id;
	                })) {
	                    return item;
	                }
	            });

	            var removed = prevPortData.filter(function(item) {
	                if (!curPortData.find(function(curPort) {
	                    return curPort.id === item.id;
	                })) {
	                    return item;
	                }
	            });

	            if (removed.length > 0) {
	                this.trigger('ports:remove', this, removed);
	            }

	            if (added.length > 0) {
	                this.trigger('ports:add', this, added);
	            }
	        }
	    }
	};

	var elementViewPortPrototype = {

	    portContainerMarkup: 'g',
	    portMarkup: [{
	        tagName: 'circle',
	        selector: 'circle',
	        attributes: {
	            'r': 10,
	            'fill': '#FFFFFF',
	            'stroke': '#000000'
	        }
	    }],
	    portLabelMarkup: [{
	        tagName: 'text',
	        selector: 'text',
	        attributes: {
	            'fill': '#000000'
	        }
	    }],
	    /** @type {Object<string, {portElement: Vectorizer, portLabelElement: Vectorizer}>} */
	    _portElementsCache: null,

	    /**
	     * @private
	     */
	    _initializePorts: function() {
	        this._cleanPortsCache();
	    },

	    /**
	     * @typedef {Object} Port
	     *
	     * @property {string} id
	     * @property {Object} position
	     * @property {Object} label
	     * @property {Object} attrs
	     * @property {string} markup
	     * @property {string} group
	     */

	    /**
	     * @private
	     */
	    _refreshPorts: function() {

	        this._removePorts();
	        this._cleanPortsCache();
	        this._renderPorts();
	    },

	    _cleanPortsCache: function() {
	        this._portElementsCache = {};
	    },

	    /**
	     * @private
	     */
	    _renderPorts: function() {

	        // references to rendered elements without z-index
	        var elementReferences = [];
	        var elem = this._getContainerElement();

	        for (var i = 0, count = elem.node.childNodes.length; i < count; i++) {
	            elementReferences.push(elem.node.childNodes[i]);
	        }

	        var portsGropsByZ = groupBy(this.model._portSettingsData.getPorts(), 'z');
	        var withoutZKey = 'auto';

	        // render non-z first
	        toArray(portsGropsByZ[withoutZKey]).forEach(function(port) {
	            var portElement = this._getPortElement(port);
	            elem.append(portElement);
	            elementReferences.push(portElement);
	        }, this);

	        var groupNames = Object.keys(portsGropsByZ);
	        for (var k = 0; k < groupNames.length; k++) {
	            var groupName = groupNames[k];
	            if (groupName !== withoutZKey) {
	                var z = parseInt(groupName, 10);
	                this._appendPorts(portsGropsByZ[groupName], z, elementReferences);
	            }
	        }

	        this._updatePorts();
	    },

	    /**
	     * @returns {V}
	     * @private
	     */
	    _getContainerElement: function() {

	        return this.rotatableNode || this.vel;
	    },

	    /**
	     * @param {Array<Port>}ports
	     * @param {number} z
	     * @param refs
	     * @private
	     */
	    _appendPorts: function(ports, z, refs) {

	        var containerElement = this._getContainerElement();
	        var portElements = toArray(ports).map(this._getPortElement, this);

	        if (refs[z] || z < 0) {
	            V(refs[Math.max(z, 0)]).before(portElements);
	        } else {
	            containerElement.append(portElements);
	        }
	    },

	    /**
	     * Try to get element from cache,
	     * @param port
	     * @returns {*}
	     * @private
	     */
	    _getPortElement: function(port) {

	        if (this._portElementsCache[port.id]) {
	            return this._portElementsCache[port.id].portElement;
	        }
	        return this._createPortElement(port);
	    },

	    findPortNode: function(portId, selector) {
	        var portCache = this._portElementsCache[portId];
	        if (!portCache) { return null; }
	        if (!selector) { return portCache.portContentElement.node; }
	        var portRoot = portCache.portElement.node;
	        var portSelectors = portCache.portSelectors;
	        var ref = this.findBySelector(selector, portRoot, portSelectors);
	        var node = ref[0]; if ( node === void 0 ) node = null;
	        return node;
	    },

	    /**
	     * @private
	     */
	    _updatePorts: function() {

	        // layout ports without group
	        this._updatePortGroup(undefined);
	        // layout ports with explicit group
	        var groupsNames = Object.keys(this.model._portSettingsData.groups);
	        groupsNames.forEach(this._updatePortGroup, this);
	    },

	    /**
	     * @private
	     */
	    _removePorts: function() {
	        invoke(this._portElementsCache, 'portElement.remove');
	    },

	    /**
	     * @param {Port} port
	     * @returns {V}
	     * @private
	     */
	    _createPortElement: function(port) {

	        var portElement;
	        var labelElement;
	        var labelSelectors;
	        var portSelectors;

	        var portContainerElement = V(this.portContainerMarkup).addClass('joint-port');

	        var portMarkup = this._getPortMarkup(port);
	        if (Array.isArray(portMarkup)) {
	            var portDoc = this.parseDOMJSON(portMarkup, portContainerElement.node);
	            var portFragment = portDoc.fragment;
	            if (portFragment.childNodes.length > 1) {
	                portElement = V('g').append(portFragment);
	            } else {
	                portElement = V(portFragment.firstChild);
	            }
	            portSelectors = portDoc.selectors;
	        } else {
	            portElement = V(portMarkup);
	            if (Array.isArray(portElement)) {
	                portElement = V('g').append(portElement);
	            }
	        }

	        if (!portElement) {
	            throw new Error('ElementView: Invalid port markup.');
	        }

	        portElement.attr({
	            'port': port.id,
	            'port-group': port.group
	        });

	        var labelMarkupDef = this._getPortLabelMarkup(port.label);
	        if (Array.isArray(labelMarkupDef)) {
	            // JSON Markup
	            var ref = this.parseDOMJSON(labelMarkupDef, portContainerElement.node);
	            var fragment = ref.fragment;
	            var selectors = ref.selectors;
	            var childCount = fragment.childNodes.length;
	            if (childCount > 0) {
	                labelSelectors = selectors;
	                labelElement = (childCount === 1) ? V(fragment.firstChild) : V('g').append(fragment);
	            }
	        } else {
	            // String Markup
	            labelElement = V(labelMarkupDef);
	            if (Array.isArray(labelElement)) {
	                labelElement = V('g').append(labelElement);
	            }
	        }

	        var portContainerSelectors;
	        if (portSelectors && labelSelectors) {
	            for (var key in labelSelectors) {
	                if (portSelectors[key] && key !== this.selector) { throw new Error('ElementView: selectors within port must be unique.'); }
	            }
	            portContainerSelectors = assign({}, portSelectors, labelSelectors);
	        } else {
	            portContainerSelectors = portSelectors || labelSelectors || {};
	        }

	        // The `portRootSelector` points to the root SVGNode of the port.
	        // Either the implicit wrapping group <g/> in case the port consist of multiple SVGNodes.
	        // Or the single SVGNode of the port.
	        var portRootSelector = 'portRoot';
	        // The `labelRootSelector` points to the root SVGNode of the label.
	        var labelRootSelector = 'labelRoot';
	        // The `labelTextSelector` points to all text SVGNodes of the label.
	        var labelTextSelector = 'labelText';

	        if (!(portRootSelector in portContainerSelectors)) {
	            portContainerSelectors[portRootSelector] = portElement.node;
	        }

	        if (labelElement) {
	            var labelNode = labelElement.node;
	            if (!(labelRootSelector in portContainerSelectors)) {
	                portContainerSelectors[labelRootSelector] = labelNode;
	            }
	            if (!(labelTextSelector in portContainerSelectors)) {
	                // If the label is a <text> element, we can use it directly.
	                // Otherwise, we need to find the <text> element within the label.
	                var labelTextNode = (labelElement.tagName() === 'TEXT')
	                    ? labelNode
	                    : Array.from(labelNode.querySelectorAll('text'));
	                portContainerSelectors[labelTextSelector] = labelTextNode;
	                if (!labelSelectors) { labelSelectors = {}; }
	                labelSelectors[labelTextSelector] = labelTextNode;
	            }
	        }

	        portContainerElement.append(portElement.addClass('joint-port-body'));
	        if (labelElement) {
	            portContainerElement.append(labelElement.addClass('joint-port-label'));
	        }

	        this._portElementsCache[port.id] = {
	            portElement: portContainerElement,
	            portLabelElement: labelElement,
	            portSelectors: portContainerSelectors,
	            portLabelSelectors: labelSelectors,
	            portContentElement: portElement,
	            portContentSelectors: portSelectors
	        };

	        return portContainerElement;
	    },

	    /**
	     * @param {string=} groupName
	     * @private
	     */
	    _updatePortGroup: function(groupName) {

	        var elementBBox = Rect(this.model.size());
	        var portsMetrics = this.model._portSettingsData.getGroupPortsMetrics(groupName, elementBBox);

	        for (var i = 0, n = portsMetrics.length; i < n; i++) {
	            var metrics = portsMetrics[i];
	            var portId = metrics.portId;
	            var cached = this._portElementsCache[portId] || {};
	            var portTransformation = metrics.portTransformation;
	            var labelTransformation = metrics.labelTransformation;
	            if (labelTransformation && cached.portLabelElement) {
	                this.updateDOMSubtreeAttributes(cached.portLabelElement.node, labelTransformation.attrs, {
	                    rootBBox: new Rect(metrics.labelSize),
	                    selectors: cached.portLabelSelectors
	                });
	                this.applyPortTransform(cached.portLabelElement, labelTransformation, (-portTransformation.angle || 0));
	            }
	            this.updateDOMSubtreeAttributes(cached.portElement.node, metrics.portAttrs, {
	                rootBBox: new Rect(metrics.portSize),
	                selectors: cached.portSelectors
	            });
	            this.applyPortTransform(cached.portElement, portTransformation);
	        }
	    },

	    /**
	     * @param {Vectorizer} element
	     * @param {{dx:number, dy:number, angle: number, attrs: Object, x:number: y:number}} transformData
	     * @param {number=} initialAngle
	     * @constructor
	     */
	    applyPortTransform: function(element, transformData, initialAngle) {

	        var matrix = V.createSVGMatrix()
	            .rotate(initialAngle || 0)
	            .translate(transformData.x || 0, transformData.y || 0)
	            .rotate(transformData.angle || 0);

	        element.transform(matrix, { absolute: true });
	    },

	    /**
	     * @param {Port} port
	     * @returns {string}
	     * @private
	     */
	    _getPortMarkup: function(port) {

	        return port.markup || this.model.get('portMarkup') || this.model.portMarkup || this.portMarkup;
	    },

	    /**
	     * @param {Object} label
	     * @returns {string}
	     * @private
	     */
	    _getPortLabelMarkup: function(label) {

	        return label.markup || this.model.get('portLabelMarkup') || this.model.portLabelMarkup || this.portLabelMarkup;
	    }
	};

	// Element base model.
	// -----------------------------

	var Element$1 = Cell.extend({

	    defaults: {
	        position: { x: 0, y: 0 },
	        size: { width: 1, height: 1 },
	        angle: 0
	    },

	    initialize: function() {

	        this._initializePorts();
	        Cell.prototype.initialize.apply(this, arguments);
	    },

	    /**
	     * @abstract
	     */
	    _initializePorts: function() {
	        // implemented in ports.js
	    },

	    _refreshPorts: function() {
	        // implemented in ports.js
	    },

	    isElement: function() {

	        return true;
	    },

	    position: function(x, y, opt) {

	        var isSetter = isNumber(y);
	        opt = (isSetter ? opt : x) || {};
	        var parentRelative = opt.parentRelative;
	        var deep = opt.deep;
	        var restrictedArea = opt.restrictedArea;


	        // option `parentRelative` for setting the position relative to the element's parent.
	        var parentPosition;
	        if (parentRelative) {

	            // Getting the parent's position requires the collection.
	            // Cell.parent() holds cell id only.
	            if (!this.graph) { throw new Error('Element must be part of a graph.'); }

	            var parent = this.getParentCell();
	            if (parent && !parent.isLink()) {
	                parentPosition = parent.get('position');
	            }
	        }

	        if (isSetter) {

	            if (parentPosition) {
	                x += parentPosition.x;
	                y += parentPosition.y;
	            }

	            if (deep || restrictedArea) {
	                var ref = this.get('position');
	                var x0 = ref.x;
	                var y0 = ref.y;
	                this.translate(x - x0, y - y0, opt);
	            } else {
	                this.set('position', { x: x, y: y }, opt);
	            }

	            return this;

	        } else { // Getter returns a geometry point.

	            var elementPosition = Point(this.get('position'));
	            return parentRelative
	                ? elementPosition.difference(parentPosition)
	                : elementPosition;
	        }
	    },

	    translate: function(tx, ty, opt) {

	        tx = tx || 0;
	        ty = ty || 0;

	        if (tx === 0 && ty === 0) {
	            // Like nothing has happened.
	            return this;
	        }

	        opt = opt || {};
	        // Pass the initiator of the translation.
	        opt.translateBy = opt.translateBy || this.id;

	        var position = this.get('position') || { x: 0, y: 0 };
	        var ra = opt.restrictedArea;
	        if (ra && opt.translateBy === this.id) {

	            if (typeof ra === 'function') {

	                var newPosition = ra.call(this, position.x + tx, position.y + ty, opt);

	                tx = newPosition.x - position.x;
	                ty = newPosition.y - position.y;

	            } else  {
	                // We are restricting the translation for the element itself only. We get
	                // the bounding box of the element including all its embeds.
	                // All embeds have to be translated the exact same way as the element.
	                var bbox = this.getBBox({ deep: true });
	                //- - - - - - - - - - - - -> ra.x + ra.width
	                // - - - -> position.x      |
	                // -> bbox.x
	                //                ▓▓▓▓▓▓▓   |
	                //         ░░░░░░░▓▓▓▓▓▓▓
	                //         ░░░░░░░░░        |
	                //   ▓▓▓▓▓▓▓▓░░░░░░░
	                //   ▓▓▓▓▓▓▓▓               |
	                //   <-dx->                     | restricted area right border
	                //         <-width->        |   ░ translated element
	                //   <- - bbox.width - ->       ▓ embedded element
	                var dx = position.x - bbox.x;
	                var dy = position.y - bbox.y;
	                // Find the maximal/minimal coordinates that the element can be translated
	                // while complies the restrictions.
	                var x = Math.max(ra.x + dx, Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx));
	                var y = Math.max(ra.y + dy, Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty));
	                // recalculate the translation taking the restrictions into account.
	                tx = x - position.x;
	                ty = y - position.y;
	            }
	        }

	        var translatedPosition = {
	            x: position.x + tx,
	            y: position.y + ty
	        };

	        // To find out by how much an element was translated in event 'change:position' handlers.
	        opt.tx = tx;
	        opt.ty = ty;

	        if (opt.transition) {

	            if (!isObject$1(opt.transition)) { opt.transition = {}; }

	            this.transition('position', translatedPosition, assign({}, opt.transition, {
	                valueFunction: interpolate.object
	            }));

	            // Recursively call `translate()` on all the embeds cells.
	            invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt);

	        } else {

	            this.startBatch('translate', opt);
	            this.set('position', translatedPosition, opt);
	            invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt);
	            this.stopBatch('translate', opt);
	        }

	        return this;
	    },

	    size: function(width, height, opt) {

	        var currentSize = this.get('size');
	        // Getter
	        // () signature
	        if (width === undefined) {
	            return {
	                width: currentSize.width,
	                height: currentSize.height
	            };
	        }
	        // Setter
	        // (size, opt) signature
	        if (isObject$1(width)) {
	            opt = height;
	            height = isNumber(width.height) ? width.height : currentSize.height;
	            width = isNumber(width.width) ? width.width : currentSize.width;
	        }

	        return this.resize(width, height, opt);
	    },

	    resize: function(width, height, opt) {

	        opt = opt || {};

	        this.startBatch('resize', opt);

	        if (opt.direction) {

	            var currentSize = this.get('size');

	            switch (opt.direction) {

	                case 'left':
	                case 'right':
	                    // Don't change height when resizing horizontally.
	                    height = currentSize.height;
	                    break;

	                case 'top':
	                case 'bottom':
	                    // Don't change width when resizing vertically.
	                    width = currentSize.width;
	                    break;
	            }

	            // Get the angle and clamp its value between 0 and 360 degrees.
	            var angle = normalizeAngle(this.get('angle') || 0);

	            // This is a rectangle in size of the un-rotated element.
	            var bbox = this.getBBox();

	            var origin;

	            if (angle) {

	                var quadrant = {
	                    'top-right': 0,
	                    'right': 0,
	                    'top-left': 1,
	                    'top': 1,
	                    'bottom-left': 2,
	                    'left': 2,
	                    'bottom-right': 3,
	                    'bottom': 3
	                }[opt.direction];

	                if (opt.absolute) {

	                    // We are taking the element's rotation into account
	                    quadrant += Math.floor((angle + 45) / 90);
	                    quadrant %= 4;
	                }

	                // Pick the corner point on the element, which meant to stay on its place before and
	                // after the rotation.
	                var fixedPoint = bbox[['bottomLeft', 'corner', 'topRight', 'origin'][quadrant]]();

	                // Find  an image of the previous indent point. This is the position, where is the
	                // point actually located on the screen.
	                var imageFixedPoint = Point(fixedPoint).rotate(bbox.center(), -angle);

	                // Every point on the element rotates around a circle with the centre of rotation
	                // in the middle of the element while the whole element is being rotated. That means
	                // that the distance from a point in the corner of the element (supposed its always rect) to
	                // the center of the element doesn't change during the rotation and therefore it equals
	                // to a distance on un-rotated element.
	                // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5.
	                var radius = Math.sqrt((width * width) + (height * height)) / 2;

	                // Now we are looking for an angle between x-axis and the line starting at image of fixed point
	                // and ending at the center of the element. We call this angle `alpha`.

	                // The image of a fixed point is located in n-th quadrant. For each quadrant passed
	                // going anti-clockwise we have to add 90 degrees. Note that the first quadrant has index 0.
	                //
	                // 3 | 2
	                // --c-- Quadrant positions around the element's center `c`
	                // 0 | 1
	                //
	                var alpha = quadrant * Math.PI / 2;

	                // Add an angle between the beginning of the current quadrant (line parallel with x-axis or y-axis
	                // going through the center of the element) and line crossing the indent of the fixed point and the center
	                // of the element. This is the angle we need but on the un-rotated element.
	                alpha += Math.atan(quadrant % 2 == 0 ? height / width : width / height);

	                // Lastly we have to deduct the original angle the element was rotated by and that's it.
	                alpha -= toRad(angle);

	                // With this angle and distance we can easily calculate the centre of the un-rotated element.
	                // Note that fromPolar constructor accepts an angle in radians.
	                var center = Point.fromPolar(radius, alpha, imageFixedPoint);

	                // The top left corner on the un-rotated element has to be half a width on the left
	                // and half a height to the top from the center. This will be the origin of rectangle
	                // we were looking for.
	                origin = Point(center).offset(width / -2, height / -2);

	            } else {
	                // calculation for the origin Point when there is no rotation of the element
	                origin = bbox.topLeft();

	                switch (opt.direction) {
	                    case 'top':
	                    case 'top-right':
	                        origin.offset(0, bbox.height - height);
	                        break;
	                    case 'left':
	                    case 'bottom-left':
	                        origin.offset(bbox.width -width, 0);
	                        break;
	                    case 'top-left':
	                        origin.offset(bbox.width - width, bbox.height - height);
	                        break;
	                }
	            }

	            // Resize the element (before re-positioning it).
	            this.set('size', { width: width, height: height }, opt);

	            // Finally, re-position the element.
	            this.position(origin.x, origin.y, opt);

	        } else {

	            // Resize the element.
	            this.set('size', { width: width, height: height }, opt);
	        }

	        this.stopBatch('resize', opt);

	        return this;
	    },

	    scale: function(sx, sy, origin, opt) {

	        var scaledBBox = this.getBBox().scale(sx, sy, origin);
	        this.startBatch('scale', opt);
	        this.position(scaledBBox.x, scaledBBox.y, opt);
	        this.resize(scaledBBox.width, scaledBBox.height, opt);
	        this.stopBatch('scale');
	        return this;
	    },

	    fitEmbeds: function(opt) {

	        return this.fitToChildren(opt);
	    },

	    fitToChildren: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        // Getting the children's size and position requires the collection.
	        // Cell.get('embeds') holds an array of cell ids only.
	        var ref = this;
	        var graph = ref.graph;
	        if (!graph) { throw new Error('Element must be part of a graph.'); }

	        var childElements = this.getEmbeddedCells().filter(function (cell) { return cell.isElement(); });
	        if (childElements.length === 0) { return this; }

	        this.startBatch('fit-embeds', opt);

	        if (opt.deep) {
	            // `opt.deep = true` means "fit to all descendants".
	            // As the first action of the fitting algorithm, recursively apply `fitToChildren()` on all descendants.
	            // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant, then go up (= this element).
	            invoke(childElements, 'fitToChildren', opt);
	        }

	        // Set new size and position of this element, based on:
	        // - union of bboxes of all children
	        // - inflated by given `opt.padding`
	        this._fitToElements(Object.assign({ elements: childElements }, opt));

	        this.stopBatch('fit-embeds');

	        return this;
	    },

	    fitParent: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        var ref = this;
	        var graph = ref.graph;
	        if (!graph) { throw new Error('Element must be part of a graph.'); }

	        // When `opt.deep = true`, we want `opt.terminator` to be the last ancestor processed.
	        // If the current element is `opt.terminator`, it means that this element has already been processed as parent so we can exit now.
	        if (opt.deep && opt.terminator && ((opt.terminator === this) || (opt.terminator === this.id))) { return this; }

	        var parentElement = this.getParentCell();
	        if (!parentElement || !parentElement.isElement()) { return this; }

	        // Get all children of parent element (i.e. this element + any sibling elements).
	        var siblingElements = parentElement.getEmbeddedCells().filter(function (cell) { return cell.isElement(); });
	        if (siblingElements.length === 0) { return this; }

	        this.startBatch('fit-parent', opt);

	        // Set new size and position of parent element, based on:
	        // - union of bboxes of all children of parent element (i.e. this element + any sibling elements)
	        // - inflated by given `opt.padding`
	        parentElement._fitToElements(Object.assign({ elements: siblingElements }, opt));

	        if (opt.deep) {
	            // `opt.deep = true` means "fit all ancestors to their respective children".
	            // As the last action of the fitting algorithm, recursively apply `fitParent()` on all ancestors.
	            // - i.e. the algorithm is applied in reverse-depth order - start from deepest descendant (= this element), then go up.
	            parentElement.fitParent(opt);
	        }

	        this.stopBatch('fit-parent');

	        return this;
	    },

	    // Assumption: This element is part of a graph.
	    _fitToElements: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        var elementsBBox = this.graph.getCellsBBox(opt.elements);
	        // If no `opt.elements` were provided, do nothing.
	        if (!elementsBBox) { return; }

	        var expandOnly = opt.expandOnly;
	        var shrinkOnly = opt.shrinkOnly;
	        // This combination is meaningless, do nothing.
	        if (expandOnly && shrinkOnly) { return; }

	        // Calculate new size and position of this element based on:
	        // - union of bboxes of `opt.elements`
	        // - inflated by `opt.padding` (if not provided, all four properties = 0)
	        var x = elementsBBox.x;
	        var y = elementsBBox.y;
	        var width = elementsBBox.width;
	        var height = elementsBBox.height;
	        var ref = normalizeSides(opt.padding);
	        var left = ref.left;
	        var right = ref.right;
	        var top = ref.top;
	        var bottom = ref.bottom;
	        x -= left;
	        y -= top;
	        width += left + right;
	        height += bottom + top;
	        var resultBBox = new Rect(x, y, width, height);

	        if (expandOnly) {
	            // Non-shrinking is enforced by taking union of this element's current bbox with bbox calculated from `opt.elements`.
	            resultBBox = this.getBBox().union(resultBBox);

	        } else if (shrinkOnly) {
	            // Non-expansion is enforced by taking intersection of this element's current bbox with bbox calculated from `opt.elements`.
	            var intersectionBBox = this.getBBox().intersect(resultBBox);
	            // If all children are outside this element's current bbox, then `intersectionBBox` is `null` - does not make sense, do nothing.
	            if (!intersectionBBox) { return; }

	            resultBBox =  intersectionBBox;
	        }

	        // Set the new size and position of this element.
	        this.set({
	            position: { x: resultBBox.x, y: resultBBox.y },
	            size: { width: resultBBox.width, height: resultBBox.height }
	        }, opt);
	    },

	    // Rotate element by `angle` degrees, optionally around `origin` point.
	    // If `origin` is not provided, it is considered to be the center of the element.
	    // If `absolute` is `true`, the `angle` is considered is absolute, i.e. it is not
	    // the difference from the previous angle.
	    rotate: function(angle, absolute, origin, opt) {

	        if (origin) {

	            var center = this.getBBox().center();
	            var size = this.get('size');
	            var position = this.get('position');
	            center.rotate(origin, this.get('angle') - angle);
	            var dx = center.x - size.width / 2 - position.x;
	            var dy = center.y - size.height / 2 - position.y;
	            this.startBatch('rotate', { angle: angle, absolute: absolute, origin: origin });
	            this.position(position.x + dx, position.y + dy, opt);
	            this.rotate(angle, absolute, null, opt);
	            this.stopBatch('rotate');

	        } else {

	            this.set('angle', absolute ? angle : (this.get('angle') + angle) % 360, opt);
	        }

	        return this;
	    },

	    angle: function() {
	        return normalizeAngle(this.get('angle') || 0);
	    },

	    getBBox: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        var ref = this;
	        var graph = ref.graph;
	        var attributes = ref.attributes;
	        var deep = opt.deep;
	        var rotate = opt.rotate;

	        if (deep && graph) {
	            // Get all the embedded elements using breadth first algorithm.
	            var elements = this.getEmbeddedCells({ deep: true, breadthFirst: true });
	            // Add the model itself.
	            elements.push(this);
	            // Note: the default of getCellsBBox() is rotate=true and can't be
	            // changed without a breaking change
	            return graph.getCellsBBox(elements, opt);
	        }

	        var angle = attributes.angle; if ( angle === void 0 ) angle = 0;
	        var attributes_position = attributes.position;
	        var x = attributes_position.x;
	        var y = attributes_position.y;
	        var attributes_size = attributes.size;
	        var width = attributes_size.width;
	        var height = attributes_size.height;
	        var bbox = new Rect(x, y, width, height);
	        if (rotate) {
	            bbox.rotateAroundCenter(angle);
	        }
	        return bbox;
	    },

	    getPointFromConnectedLink: function(link, endType) {
	        // Center of the model
	        var bbox = this.getBBox();
	        var center = bbox.center();
	        // Center of a port
	        var endDef = link.get(endType);
	        if (!endDef) { return center; }
	        var portId = endDef.port;
	        if (!portId || !this.hasPort(portId)) { return center; }
	        var portGroup = this.portProp(portId, ['group']);
	        var portsPositions = this.getPortsPositions(portGroup);
	        var portCenter = new Point(portsPositions[portId]).offset(bbox.origin());
	        var angle = this.angle();
	        if (angle) { portCenter.rotate(center, -angle); }
	        return portCenter;
	    }
	});

	assign(Element$1.prototype, elementPortPrototype);

	var GraphCells = Backbone.Collection.extend({

	    initialize: function(models, opt) {

	        // Set the optional namespace where all model classes are defined.
	        if (opt.cellNamespace) {
	            this.cellNamespace = opt.cellNamespace;
	        } else {
	            /* eslint-disable no-undef */
	            this.cellNamespace = typeof joint !== 'undefined' && has$2(joint, 'shapes') ? joint.shapes : null;
	            /* eslint-enable no-undef */
	        }


	        this.graph = opt.graph;
	    },

	    model: function(attrs, opt) {

	        var collection = opt.collection;
	        var namespace = collection.cellNamespace;

	        // Find the model class in the namespace or use the default one.
	        var ModelClass = (attrs.type === 'link')
	            ? Link
	            : getByPath(namespace, attrs.type, '.') || Element$1;

	        var cell = new ModelClass(attrs, opt);
	        // Add a reference to the graph. It is necessary to do this here because this is the earliest place
	        // where a new model is created from a plain JS object. For other objects, see `joint.dia.Graph>>_prepareCell()`.
	        if (!opt.dry) {
	            cell.graph = collection.graph;
	        }

	        return cell;
	    },

	    // `comparator` makes it easy to sort cells based on their `z` index.
	    comparator: function(model) {

	        return model.get('z') || 0;
	    }
	});


	var Graph = Backbone.Model.extend({

	    initialize: function(attrs, opt) {

	        opt = opt || {};

	        // Passing `cellModel` function in the options object to graph allows for
	        // setting models based on attribute objects. This is especially handy
	        // when processing JSON graphs that are in a different than JointJS format.
	        var cells = new GraphCells([], {
	            model: opt.cellModel,
	            cellNamespace: opt.cellNamespace,
	            graph: this
	        });
	        Backbone.Model.prototype.set.call(this, 'cells', cells);

	        // Make all the events fired in the `cells` collection available.
	        // to the outside world.
	        cells.on('all', this.trigger, this);

	        // Backbone automatically doesn't trigger re-sort if models attributes are changed later when
	        // they're already in the collection. Therefore, we're triggering sort manually here.
	        this.on('change:z', this._sortOnChangeZ, this);

	        // `joint.dia.Graph` keeps an internal data structure (an adjacency list)
	        // for fast graph queries. All changes that affect the structure of the graph
	        // must be reflected in the `al` object. This object provides fast answers to
	        // questions such as "what are the neighbours of this node" or "what
	        // are the sibling links of this link".

	        // Outgoing edges per node. Note that we use a hash-table for the list
	        // of outgoing edges for a faster lookup.
	        // [nodeId] -> Object [edgeId] -> true
	        this._out = {};
	        // Ingoing edges per node.
	        // [nodeId] -> Object [edgeId] -> true
	        this._in = {};
	        // `_nodes` is useful for quick lookup of all the elements in the graph, without
	        // having to go through the whole cells array.
	        // [node ID] -> true
	        this._nodes = {};
	        // `_edges` is useful for quick lookup of all the links in the graph, without
	        // having to go through the whole cells array.
	        // [edgeId] -> true
	        this._edges = {};

	        this._batches = {};

	        cells.on('add', this._restructureOnAdd, this);
	        cells.on('remove', this._restructureOnRemove, this);
	        cells.on('reset', this._restructureOnReset, this);
	        cells.on('change:source', this._restructureOnChangeSource, this);
	        cells.on('change:target', this._restructureOnChangeTarget, this);
	        cells.on('remove', this._removeCell, this);
	    },

	    _sortOnChangeZ: function() {

	        this.get('cells').sort();
	    },

	    _restructureOnAdd: function(cell) {

	        if (cell.isLink()) {
	            this._edges[cell.id] = true;
	            var ref = cell.attributes;
	            var source = ref.source;
	            var target = ref.target;
	            if (source.id) {
	                (this._out[source.id] || (this._out[source.id] = {}))[cell.id] = true;
	            }
	            if (target.id) {
	                (this._in[target.id] || (this._in[target.id] = {}))[cell.id] = true;
	            }
	        } else {
	            this._nodes[cell.id] = true;
	        }
	    },

	    _restructureOnRemove: function(cell) {

	        if (cell.isLink()) {
	            delete this._edges[cell.id];
	            var ref = cell.attributes;
	            var source = ref.source;
	            var target = ref.target;
	            if (source.id && this._out[source.id] && this._out[source.id][cell.id]) {
	                delete this._out[source.id][cell.id];
	            }
	            if (target.id && this._in[target.id] && this._in[target.id][cell.id]) {
	                delete this._in[target.id][cell.id];
	            }
	        } else {
	            delete this._nodes[cell.id];
	        }
	    },

	    _restructureOnReset: function(cells) {

	        // Normalize into an array of cells. The original `cells` is GraphCells Backbone collection.
	        cells = cells.models;

	        this._out = {};
	        this._in = {};
	        this._nodes = {};
	        this._edges = {};

	        cells.forEach(this._restructureOnAdd, this);
	    },

	    _restructureOnChangeSource: function(link) {

	        var prevSource = link.previous('source');
	        if (prevSource.id && this._out[prevSource.id]) {
	            delete this._out[prevSource.id][link.id];
	        }
	        var source = link.attributes.source;
	        if (source.id) {
	            (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true;
	        }
	    },

	    _restructureOnChangeTarget: function(link) {

	        var prevTarget = link.previous('target');
	        if (prevTarget.id && this._in[prevTarget.id]) {
	            delete this._in[prevTarget.id][link.id];
	        }
	        var target = link.get('target');
	        if (target.id) {
	            (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true;
	        }
	    },

	    // Return all outbound edges for the node. Return value is an object
	    // of the form: [edgeId] -> true
	    getOutboundEdges: function(node) {

	        return (this._out && this._out[node]) || {};
	    },

	    // Return all inbound edges for the node. Return value is an object
	    // of the form: [edgeId] -> true
	    getInboundEdges: function(node) {

	        return (this._in && this._in[node]) || {};
	    },

	    toJSON: function() {

	        // Backbone does not recursively call `toJSON()` on attributes that are themselves models/collections.
	        // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitly.
	        var json = Backbone.Model.prototype.toJSON.apply(this, arguments);
	        json.cells = this.get('cells').toJSON();
	        return json;
	    },

	    fromJSON: function(json, opt) {

	        if (!json.cells) {

	            throw new Error('Graph JSON must contain cells array.');
	        }

	        return this.set(json, opt);
	    },

	    set: function(key, val, opt) {

	        var attrs;

	        // Handle both `key`, value and {key: value} style arguments.
	        if (typeof key === 'object') {
	            attrs = key;
	            opt = val;
	        } else {
	            (attrs = {})[key] = val;
	        }

	        // Make sure that `cells` attribute is handled separately via resetCells().
	        if (attrs.hasOwnProperty('cells')) {
	            this.resetCells(attrs.cells, opt);
	            attrs = omit(attrs, 'cells');
	        }

	        // The rest of the attributes are applied via original set method.
	        return Backbone.Model.prototype.set.call(this, attrs, opt);
	    },

	    clear: function(opt) {

	        opt = assign({}, opt, { clear: true });

	        var collection = this.get('cells');

	        if (collection.length === 0) { return this; }

	        this.startBatch('clear', opt);

	        // The elements come after the links.
	        var cells = collection.sortBy(function(cell) {
	            return cell.isLink() ? 1 : 2;
	        });

	        do {

	            // Remove all the cells one by one.
	            // Note that all the links are removed first, so it's
	            // safe to remove the elements without removing the connected
	            // links first.
	            cells.shift().remove(opt);

	        } while (cells.length > 0);

	        this.stopBatch('clear');

	        return this;
	    },

	    _prepareCell: function(cell, opt) {

	        var attrs;
	        if (cell instanceof Backbone.Model) {
	            attrs = cell.attributes;
	            if (!cell.graph && (!opt || !opt.dry)) {
	                // An element can not be member of more than one graph.
	                // A cell stops being the member of the graph after it's explicitly removed.
	                cell.graph = this;
	            }
	        } else {
	            // In case we're dealing with a plain JS object, we have to set the reference
	            // to the `graph` right after the actual model is created. This happens in the `model()` function
	            // of `joint.dia.GraphCells`.
	            attrs = cell;
	        }

	        if (!isString(attrs.type)) {
	            throw new TypeError('dia.Graph: cell type must be a string.');
	        }

	        return cell;
	    },

	    minZIndex: function() {

	        var firstCell = this.get('cells').first();
	        return firstCell ? (firstCell.get('z') || 0) : 0;
	    },

	    maxZIndex: function() {

	        var lastCell = this.get('cells').last();
	        return lastCell ? (lastCell.get('z') || 0) : 0;
	    },

	    addCell: function(cell, opt) {

	        if (Array.isArray(cell)) {

	            return this.addCells(cell, opt);
	        }

	        if (cell instanceof Backbone.Model) {

	            if (!cell.has('z')) {
	                cell.set('z', this.maxZIndex() + 1);
	            }

	        } else if (cell.z === undefined) {

	            cell.z = this.maxZIndex() + 1;
	        }

	        this.get('cells').add(this._prepareCell(cell, opt), opt || {});

	        return this;
	    },

	    addCells: function(cells, opt) {

	        if (cells.length === 0) { return this; }

	        cells = flattenDeep(cells);
	        opt.maxPosition = opt.position = cells.length - 1;

	        this.startBatch('add', opt);
	        cells.forEach(function(cell) {
	            this.addCell(cell, opt);
	            opt.position--;
	        }, this);
	        this.stopBatch('add', opt);

	        return this;
	    },

	    // When adding a lot of cells, it is much more efficient to
	    // reset the entire cells collection in one go.
	    // Useful for bulk operations and optimizations.
	    resetCells: function(cells, opt) {

	        var preparedCells = toArray(cells).map(function(cell) {
	            return this._prepareCell(cell, opt);
	        }, this);
	        this.get('cells').reset(preparedCells, opt);

	        return this;
	    },

	    removeCells: function(cells, opt) {

	        if (cells.length) {

	            this.startBatch('remove');
	            invoke(cells, 'remove', opt);
	            this.stopBatch('remove');
	        }

	        return this;
	    },

	    _removeCell: function(cell, collection, options) {

	        options = options || {};

	        if (!options.clear) {
	            // Applications might provide a `disconnectLinks` option set to `true` in order to
	            // disconnect links when a cell is removed rather then removing them. The default
	            // is to remove all the associated links.
	            if (options.disconnectLinks) {

	                this.disconnectLinks(cell, options);

	            } else {

	                this.removeLinks(cell, options);
	            }
	        }
	        // Silently remove the cell from the cells collection. Silently, because
	        // `joint.dia.Cell.prototype.remove` already triggers the `remove` event which is
	        // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events
	        // would be triggered on the graph model.
	        this.get('cells').remove(cell, { silent: true });

	        if (cell.graph === this) {
	            // Remove the element graph reference only if the cell is the member of this graph.
	            cell.graph = null;
	        }
	    },

	    // Get a cell by `id`.
	    getCell: function(id) {

	        return this.get('cells').get(id);
	    },

	    getCells: function() {

	        return this.get('cells').toArray();
	    },

	    getElements: function() {

	        return this.get('cells').filter(function (cell) { return cell.isElement(); });
	    },

	    getLinks: function() {

	        return this.get('cells').filter(function (cell) { return cell.isLink(); });
	    },

	    getFirstCell: function() {

	        return this.get('cells').first();
	    },

	    getLastCell: function() {

	        return this.get('cells').last();
	    },

	    // Get all inbound and outbound links connected to the cell `model`.
	    getConnectedLinks: function(model, opt) {

	        opt = opt || {};

	        var indirect = opt.indirect;
	        var inbound = opt.inbound;
	        var outbound = opt.outbound;
	        if ((inbound === undefined) && (outbound === undefined)) {
	            inbound = outbound = true;
	        }

	        // the final array of connected link models
	        var links = [];
	        // a hash table of connected edges of the form: [edgeId] -> true
	        // used for quick lookups to check if we already added a link
	        var edges = {};

	        if (outbound) {
	            addOutbounds(this, model);
	        }
	        if (inbound) {
	            addInbounds(this, model);
	        }

	        function addOutbounds(graph, model) {
	            forIn(graph.getOutboundEdges(model.id), function(_, edge) {
	                // skip links that were already added
	                // (those must be self-loop links)
	                // (because they are inbound and outbound edges of the same two elements)
	                if (edges[edge]) { return; }
	                var link = graph.getCell(edge);
	                links.push(link);
	                edges[edge] = true;
	                if (indirect) {
	                    if (inbound) { addInbounds(graph, link); }
	                    if (outbound) { addOutbounds(graph, link); }
	                }
	            }.bind(graph));
	            if (indirect && model.isLink()) {
	                var outCell = model.getTargetCell();
	                if (outCell && outCell.isLink()) {
	                    if (!edges[outCell.id]) {
	                        links.push(outCell);
	                        addOutbounds(graph, outCell);
	                    }
	                }
	            }
	        }

	        function addInbounds(graph, model) {
	            forIn(graph.getInboundEdges(model.id), function(_, edge) {
	                // skip links that were already added
	                // (those must be self-loop links)
	                // (because they are inbound and outbound edges of the same two elements)
	                if (edges[edge]) { return; }
	                var link = graph.getCell(edge);
	                links.push(link);
	                edges[edge] = true;
	                if (indirect) {
	                    if (inbound) { addInbounds(graph, link); }
	                    if (outbound) { addOutbounds(graph, link); }
	                }
	            }.bind(graph));
	            if (indirect && model.isLink()) {
	                var inCell = model.getSourceCell();
	                if (inCell && inCell.isLink()) {
	                    if (!edges[inCell.id]) {
	                        links.push(inCell);
	                        addInbounds(graph, inCell);
	                    }
	                }
	            }
	        }

	        // if `deep` option is `true`, check also all the links that are connected to any of the descendant cells
	        if (opt.deep) {

	            var embeddedCells = model.getEmbeddedCells({ deep: true });

	            // in the first round, we collect all the embedded elements
	            var embeddedElements = {};
	            embeddedCells.forEach(function(cell) {
	                if (cell.isElement()) {
	                    embeddedElements[cell.id] = true;
	                }
	            });

	            embeddedCells.forEach(function(cell) {
	                if (cell.isLink()) { return; }
	                if (outbound) {
	                    forIn(this.getOutboundEdges(cell.id), function(exists, edge) {
	                        if (!edges[edge]) {
	                            var edgeCell = this.getCell(edge);
	                            var ref = edgeCell.attributes;
	                            var source = ref.source;
	                            var target = ref.target;
	                            var sourceId = source.id;
	                            var targetId = target.id;

	                            // if `includeEnclosed` option is falsy, skip enclosed links
	                            if (!opt.includeEnclosed
	                                && (sourceId && embeddedElements[sourceId])
	                                && (targetId && embeddedElements[targetId])) {
	                                return;
	                            }

	                            links.push(this.getCell(edge));
	                            edges[edge] = true;
	                        }
	                    }.bind(this));
	                }
	                if (inbound) {
	                    forIn(this.getInboundEdges(cell.id), function(exists, edge) {
	                        if (!edges[edge]) {
	                            var edgeCell = this.getCell(edge);
	                            var ref = edgeCell.attributes;
	                            var source = ref.source;
	                            var target = ref.target;
	                            var sourceId = source.id;
	                            var targetId = target.id;

	                            // if `includeEnclosed` option is falsy, skip enclosed links
	                            if (!opt.includeEnclosed
	                                && (sourceId && embeddedElements[sourceId])
	                                && (targetId && embeddedElements[targetId])) {
	                                return;
	                            }

	                            links.push(this.getCell(edge));
	                            edges[edge] = true;
	                        }
	                    }.bind(this));
	                }
	            }, this);
	        }

	        return links;
	    },

	    getNeighbors: function(model, opt) {

	        opt || (opt = {});

	        var inbound = opt.inbound;
	        var outbound = opt.outbound;
	        if (inbound === undefined && outbound === undefined) {
	            inbound = outbound = true;
	        }

	        var neighbors = this.getConnectedLinks(model, opt).reduce(function(res, link) {

	            var ref = link.attributes;
	            var source = ref.source;
	            var target = ref.target;
	            var loop = link.hasLoop(opt);

	            // Discard if it is a point, or if the neighbor was already added.
	            if (inbound && has$2(source, 'id') && !res[source.id]) {

	                var sourceElement = this.getCell(source.id);
	                if (sourceElement.isElement()) {
	                    if (loop || (sourceElement && sourceElement !== model && (!opt.deep || !sourceElement.isEmbeddedIn(model)))) {
	                        res[source.id] = sourceElement;
	                    }
	                }
	            }

	            // Discard if it is a point, or if the neighbor was already added.
	            if (outbound && has$2(target, 'id') && !res[target.id]) {

	                var targetElement = this.getCell(target.id);
	                if (targetElement.isElement()) {
	                    if (loop || (targetElement && targetElement !== model && (!opt.deep || !targetElement.isEmbeddedIn(model)))) {
	                        res[target.id] = targetElement;
	                    }
	                }
	            }

	            return res;
	        }.bind(this), {});

	        if (model.isLink()) {
	            if (inbound) {
	                var sourceCell = model.getSourceCell();
	                if (sourceCell && sourceCell.isElement() && !neighbors[sourceCell.id]) {
	                    neighbors[sourceCell.id] = sourceCell;
	                }
	            }
	            if (outbound) {
	                var targetCell = model.getTargetCell();
	                if (targetCell && targetCell.isElement() && !neighbors[targetCell.id]) {
	                    neighbors[targetCell.id] = targetCell;
	                }
	            }
	        }

	        return toArray(neighbors);
	    },

	    getCommonAncestor: function(/* cells */) {

	        var cellsAncestors = Array.from(arguments).map(function(cell) {

	            var ancestors = [];
	            var parentId = cell.get('parent');

	            while (parentId) {

	                ancestors.push(parentId);
	                parentId = this.getCell(parentId).get('parent');
	            }

	            return ancestors;

	        }, this);

	        cellsAncestors = cellsAncestors.sort(function(a, b) {
	            return a.length - b.length;
	        });

	        var commonAncestor = toArray(cellsAncestors.shift()).find(function(ancestor) {
	            return cellsAncestors.every(function(cellAncestors) {
	                return cellAncestors.includes(ancestor);
	            });
	        });

	        return this.getCell(commonAncestor);
	    },

	    // Find the whole branch starting at `element`.
	    // If `opt.deep` is `true`, take into account embedded elements too.
	    // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search.
	    getSuccessors: function(element, opt) {

	        opt = opt || {};
	        var res = [];
	        // Modify the options so that it includes the `outbound` neighbors only. In other words, search forwards.
	        this.search(element, function(el) {
	            if (el !== element) {
	                res.push(el);
	            }
	        }, assign({}, opt, { outbound: true }));
	        return res;
	    },

	    cloneCells: cloneCells,
	    // Clone the whole subgraph (including all the connected links whose source/target is in the subgraph).
	    // If `opt.deep` is `true`, also take into account all the embedded cells of all the subgraph cells.
	    // Return a map of the form: [original cell ID] -> [clone].
	    cloneSubgraph: function(cells, opt) {

	        var subgraph = this.getSubgraph(cells, opt);
	        return this.cloneCells(subgraph);
	    },

	    // Return `cells` and all the connected links that connect cells in the `cells` array.
	    // If `opt.deep` is `true`, return all the cells including all their embedded cells
	    // and all the links that connect any of the returned cells.
	    // For example, for a single shallow element, the result is that very same element.
	    // For two elements connected with a link: `A --- L ---> B`, the result for
	    // `getSubgraph([A, B])` is `[A, L, B]`. The same goes for `getSubgraph([L])`, the result is again `[A, L, B]`.
	    getSubgraph: function(cells, opt) {

	        opt = opt || {};

	        var subgraph = [];
	        // `cellMap` is used for a quick lookup of existence of a cell in the `cells` array.
	        var cellMap = {};
	        var elements = [];
	        var links = [];

	        toArray(cells).forEach(function(cell) {
	            if (!cellMap[cell.id]) {
	                subgraph.push(cell);
	                cellMap[cell.id] = cell;
	                if (cell.isLink()) {
	                    links.push(cell);
	                } else {
	                    elements.push(cell);
	                }
	            }

	            if (opt.deep) {
	                var embeds = cell.getEmbeddedCells({ deep: true });
	                embeds.forEach(function(embed) {
	                    if (!cellMap[embed.id]) {
	                        subgraph.push(embed);
	                        cellMap[embed.id] = embed;
	                        if (embed.isLink()) {
	                            links.push(embed);
	                        } else {
	                            elements.push(embed);
	                        }
	                    }
	                });
	            }
	        });

	        links.forEach(function(link) {
	            // For links, return their source & target (if they are elements - not points).
	            var ref = link.attributes;
	            var source = ref.source;
	            var target = ref.target;
	            if (source.id && !cellMap[source.id]) {
	                var sourceElement = this.getCell(source.id);
	                subgraph.push(sourceElement);
	                cellMap[sourceElement.id] = sourceElement;
	                elements.push(sourceElement);
	            }
	            if (target.id && !cellMap[target.id]) {
	                var targetElement = this.getCell(target.id);
	                subgraph.push(this.getCell(target.id));
	                cellMap[targetElement.id] = targetElement;
	                elements.push(targetElement);
	            }
	        }, this);

	        elements.forEach(function(element) {
	            // For elements, include their connected links if their source/target is in the subgraph;
	            var links = this.getConnectedLinks(element, opt);
	            links.forEach(function(link) {
	                var ref = link.attributes;
	                var source = ref.source;
	                var target = ref.target;
	                if (!cellMap[link.id] && source.id && cellMap[source.id] && target.id && cellMap[target.id]) {
	                    subgraph.push(link);
	                    cellMap[link.id] = link;
	                }
	            });
	        }, this);

	        return subgraph;
	    },

	    // Find all the predecessors of `element`. This is a reverse operation of `getSuccessors()`.
	    // If `opt.deep` is `true`, take into account embedded elements too.
	    // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search.
	    getPredecessors: function(element, opt) {

	        opt = opt || {};
	        var res = [];
	        // Modify the options so that it includes the `inbound` neighbors only. In other words, search backwards.
	        this.search(element, function(el) {
	            if (el !== element) {
	                res.push(el);
	            }
	        }, assign({}, opt, { inbound: true }));
	        return res;
	    },

	    // Perform search on the graph.
	    // If `opt.breadthFirst` is `true`, use the Breadth-first Search algorithm, otherwise use Depth-first search.
	    // By setting `opt.inbound` to `true`, you can reverse the direction of the search.
	    // If `opt.deep` is `true`, take into account embedded elements too.
	    // `iteratee` is a function of the form `function(element) {}`.
	    // If `iteratee` explicitly returns `false`, the searching stops.
	    search: function(element, iteratee, opt) {

	        opt = opt || {};
	        if (opt.breadthFirst) {
	            this.bfs(element, iteratee, opt);
	        } else {
	            this.dfs(element, iteratee, opt);
	        }
	    },

	    // Breadth-first search.
	    // If `opt.deep` is `true`, take into account embedded elements too.
	    // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions).
	    // `iteratee` is a function of the form `function(element, distance) {}`.
	    // where `element` is the currently visited element and `distance` is the distance of that element
	    // from the root `element` passed the `bfs()`, i.e. the element we started the search from.
	    // Note that the `distance` is not the shortest or longest distance, it is simply the number of levels
	    // crossed till we visited the `element` for the first time. It is especially useful for tree graphs.
	    // If `iteratee` explicitly returns `false`, the searching stops.
	    bfs: function(element, iteratee, opt) {
	        if ( opt === void 0 ) opt = {};


	        var visited = {};
	        var distance = {};
	        var queue = [];

	        queue.push(element);
	        distance[element.id] = 0;

	        while (queue.length > 0) {
	            var next = queue.shift();
	            if (visited[next.id]) { continue; }
	            visited[next.id] = true;
	            if (iteratee.call(this, next, distance[next.id]) === false) { continue; }
	            var neighbors = this.getNeighbors(next, opt);
	            for (var i = 0, n = neighbors.length; i < n; i++) {
	                var neighbor = neighbors[i];
	                distance[neighbor.id] = distance[next.id] + 1;
	                queue.push(neighbor);
	            }
	        }
	    },

	    // Depth-first search.
	    // If `opt.deep` is `true`, take into account embedded elements too.
	    // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions).
	    // `iteratee` is a function of the form `function(element, distance) {}`.
	    // If `iteratee` explicitly returns `false`, the search stops.
	    dfs: function(element, iteratee, opt) {
	        if ( opt === void 0 ) opt = {};


	        var visited = {};
	        var distance = {};
	        var queue = [];

	        queue.push(element);
	        distance[element.id] = 0;

	        while (queue.length > 0) {
	            var next = queue.pop();
	            if (visited[next.id]) { continue; }
	            visited[next.id] = true;
	            if (iteratee.call(this, next, distance[next.id]) === false) { continue; }
	            var neighbors = this.getNeighbors(next, opt);
	            var lastIndex = queue.length;
	            for (var i = 0, n = neighbors.length; i < n; i++) {
	                var neighbor = neighbors[i];
	                distance[neighbor.id] = distance[next.id] + 1;
	                queue.splice(lastIndex, 0, neighbor);
	            }
	        }
	    },

	    // Get all the roots of the graph. Time complexity: O(|V|).
	    getSources: function() {

	        var sources = [];
	        forIn(this._nodes, function(exists, node) {
	            if (!this._in[node] || isEmpty(this._in[node])) {
	                sources.push(this.getCell(node));
	            }
	        }.bind(this));
	        return sources;
	    },

	    // Get all the leafs of the graph. Time complexity: O(|V|).
	    getSinks: function() {

	        var sinks = [];
	        forIn(this._nodes, function(exists, node) {
	            if (!this._out[node] || isEmpty(this._out[node])) {
	                sinks.push(this.getCell(node));
	            }
	        }.bind(this));
	        return sinks;
	    },

	    // Return `true` if `element` is a root. Time complexity: O(1).
	    isSource: function(element) {

	        return !this._in[element.id] || isEmpty(this._in[element.id]);
	    },

	    // Return `true` if `element` is a leaf. Time complexity: O(1).
	    isSink: function(element) {

	        return !this._out[element.id] || isEmpty(this._out[element.id]);
	    },

	    // Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise.
	    isSuccessor: function(elementA, elementB) {

	        var isSuccessor = false;
	        this.search(elementA, function(element) {
	            if (element === elementB && element !== elementA) {
	                isSuccessor = true;
	                return false;
	            }
	        }, { outbound: true });
	        return isSuccessor;
	    },

	    // Return `true` is `elementB` is a predecessor of `elementA`. Return `false` otherwise.
	    isPredecessor: function(elementA, elementB) {

	        var isPredecessor = false;
	        this.search(elementA, function(element) {
	            if (element === elementB && element !== elementA) {
	                isPredecessor = true;
	                return false;
	            }
	        }, { inbound: true });
	        return isPredecessor;
	    },

	    // Return `true` is `elementB` is a neighbor of `elementA`. Return `false` otherwise.
	    // `opt.deep` controls whether to take into account embedded elements as well. See `getNeighbors()`
	    // for more details.
	    // If `opt.outbound` is set to `true`, return `true` only if `elementB` is a successor neighbor.
	    // Similarly, if `opt.inbound` is set to `true`, return `true` only if `elementB` is a predecessor neighbor.
	    isNeighbor: function(elementA, elementB, opt) {

	        opt = opt || {};

	        var inbound = opt.inbound;
	        var outbound = opt.outbound;
	        if ((inbound === undefined) && (outbound === undefined)) {
	            inbound = outbound = true;
	        }

	        var isNeighbor = false;

	        this.getConnectedLinks(elementA, opt).forEach(function(link) {

	            var ref = link.attributes;
	            var source = ref.source;
	            var target = ref.target;

	            // Discard if it is a point.
	            if (inbound && has$2(source, 'id') && (source.id === elementB.id)) {
	                isNeighbor = true;
	                return false;
	            }

	            // Discard if it is a point, or if the neighbor was already added.
	            if (outbound && has$2(target, 'id') && (target.id === elementB.id)) {
	                isNeighbor = true;
	                return false;
	            }
	        });

	        return isNeighbor;
	    },

	    // Disconnect links connected to the cell `model`.
	    disconnectLinks: function(model, opt) {

	        this.getConnectedLinks(model).forEach(function(link) {

	            link.set((link.attributes.source.id === model.id ? 'source' : 'target'), { x: 0, y: 0 }, opt);
	        });
	    },

	    // Remove links connected to the cell `model` completely.
	    removeLinks: function(model, opt) {

	        invoke(this.getConnectedLinks(model), 'remove', opt);
	    },

	    // Find all elements at given point
	    findModelsFromPoint: function(p) {
	        return this.getElements().filter(function (el) { return el.getBBox({ rotate: true }).containsPoint(p); });
	    },

	    // Find all elements in given area
	    findModelsInArea: function(rect, opt) {
	        if ( opt === void 0 ) opt = {};

	        var r = new Rect(rect);
	        var strict = opt.strict; if ( strict === void 0 ) strict = false;
	        var method = strict ? 'containsRect' : 'intersect';
	        return this.getElements().filter(function (el) { return r[method](el.getBBox({ rotate: true })); });
	    },

	    // Find all elements under the given element.
	    findModelsUnderElement: function(element, opt) {
	        if ( opt === void 0 ) opt = {};

	        var searchBy = opt.searchBy; if ( searchBy === void 0 ) searchBy = 'bbox';
	        var bbox = element.getBBox().rotateAroundCenter(element.angle());
	        var elements = (searchBy === 'bbox')
	            ? this.findModelsInArea(bbox)
	            : this.findModelsFromPoint(getRectPoint(bbox, searchBy));
	        // don't account element itself or any of its descendants
	        return elements.filter(function (el) { return element.id !== el.id && !el.isEmbeddedIn(element); });
	    },

	    // Return bounding box of all elements.
	    getBBox: function() {

	        return this.getCellsBBox(this.getCells());
	    },

	    // Return the bounding box of all cells in array provided.
	    getCellsBBox: function(cells, opt) {
	        if ( opt === void 0 ) opt = {};

	        var rotate = opt.rotate; if ( rotate === void 0 ) rotate = true;
	        return toArray(cells).reduce(function(memo, cell) {
	            var rect = cell.getBBox({ rotate: rotate });
	            if (!rect) { return memo; }
	            if (memo) {
	                return memo.union(rect);
	            }
	            return rect;
	        }, null);
	    },

	    translate: function(dx, dy, opt) {

	        // Don't translate cells that are embedded in any other cell.
	        var cells = this.getCells().filter(function(cell) {
	            return !cell.isEmbedded();
	        });

	        invoke(cells, 'translate', dx, dy, opt);

	        return this;
	    },

	    resize: function(width, height, opt) {

	        return this.resizeCells(width, height, this.getCells(), opt);
	    },

	    resizeCells: function(width, height, cells, opt) {

	        // `getBBox` method returns `null` if no elements provided.
	        // i.e. cells can be an array of links
	        var bbox = this.getCellsBBox(cells);
	        if (bbox) {
	            var sx = Math.max(width / bbox.width, 0);
	            var sy = Math.max(height / bbox.height, 0);
	            invoke(cells, 'scale', sx, sy, bbox.origin(), opt);
	        }

	        return this;
	    },

	    startBatch: function(name, data) {

	        data = data || {};
	        this._batches[name] = (this._batches[name] || 0) + 1;

	        return this.trigger('batch:start', assign({}, data, { batchName: name }));
	    },

	    stopBatch: function(name, data) {

	        data = data || {};
	        this._batches[name] = (this._batches[name] || 0) - 1;

	        return this.trigger('batch:stop', assign({}, data, { batchName: name }));
	    },

	    hasActiveBatch: function(name) {

	        var batches = this._batches;
	        var names;

	        if (arguments.length === 0) {
	            names = Object.keys(batches);
	        } else if (Array.isArray(name)) {
	            names = name;
	        } else {
	            names = [name];
	        }

	        return names.some(function (batch) { return batches[batch] > 0; });
	    }

	}, {

	    validations: {

	        multiLinks: function(graph, link) {

	            // Do not allow multiple links to have the same source and target.
	            var ref = link.attributes;
	            var source = ref.source;
	            var target = ref.target;

	            if (source.id && target.id) {

	                var sourceModel = link.getSourceCell();
	                if (sourceModel) {

	                    var connectedLinks = graph.getConnectedLinks(sourceModel, { outbound: true });
	                    var sameLinks = connectedLinks.filter(function(_link) {

	                        var ref = _link.attributes;
	                        var _source = ref.source;
	                        var _target = ref.target;
	                        return _source && _source.id === source.id &&
	                            (!_source.port || (_source.port === source.port)) &&
	                            _target && _target.id === target.id &&
	                            (!_target.port || (_target.port === target.port));

	                    });

	                    if (sameLinks.length > 1) {
	                        return false;
	                    }
	                }
	            }

	            return true;
	        },

	        linkPinning: function(_graph, link) {
	            var ref = link.attributes;
	            var source = ref.source;
	            var target = ref.target;
	            return source.id && target.id;
	        }
	    }

	});

	wrapWith(Graph.prototype, ['resetCells', 'addCells', 'removeCells'], wrappers.cells);

	var views = {};

	var View = Backbone.View.extend({

	    options: {},
	    theme: null,
	    themeClassNamePrefix: addClassNamePrefix('theme-'),
	    requireSetThemeOverride: false,
	    defaultTheme: config.defaultTheme,
	    children: null,
	    childNodes: null,

	    DETACHABLE: true,
	    UPDATE_PRIORITY: 2,
	    FLAG_INSERT: 1<<30,
	    FLAG_REMOVE: 1<<29,
	    FLAG_INIT: 1<<28,

	    constructor: function(options) {

	        this.requireSetThemeOverride = options && !!options.theme;
	        this.options = assign({}, this.options, options);

	        Backbone.View.call(this, options);
	    },

	    initialize: function() {

	        views[this.cid] = this;

	        this.setTheme(this.options.theme || this.defaultTheme);
	        this.init();
	    },

	    unmount: function() {
	        if (this.svgElement) {
	            this.vel.remove();
	        } else {
	            this.$el.remove();
	        }
	    },

	    isMounted: function() {
	        return this.el.parentNode !== null;
	    },

	    renderChildren: function(children) {
	        children || (children = result(this, 'children'));
	        if (children) {
	            var isSVG = this.svgElement;
	            var namespace = V.namespace[isSVG ? 'svg' : 'xhtml'];
	            var doc = parseDOMJSON(children, namespace);
	            (isSVG ? this.vel : this.$el).empty().append(doc.fragment);
	            this.childNodes = doc.selectors;
	        }
	        return this;
	    },

	    findAttribute: function(attributeName, node) {

	        var currentNode = node;

	        while (currentNode && currentNode.nodeType === 1) {
	            var attributeValue = currentNode.getAttribute(attributeName);
	            // attribute found
	            if (attributeValue) { return attributeValue; }
	            // do not climb up the DOM
	            if (currentNode === this.el) { return null; }
	            // try parent node
	            currentNode = currentNode.parentNode;
	        }

	        return null;
	    },

	    // Override the Backbone `_ensureElement()` method in order to create an
	    // svg element (e.g., `<g>`) node that wraps all the nodes of the Cell view.
	    // Expose class name setter as a separate method.
	    _ensureElement: function() {
	        if (!this.el) {
	            var tagName = result(this, 'tagName');
	            var attrs = assign({}, result(this, 'attributes'));
	            var style = assign({}, result(this, 'style'));
	            if (this.id) { attrs.id = result(this, 'id'); }
	            this.setElement(this._createElement(tagName));
	            this._setAttributes(attrs);
	            this._setStyle(style);
	        } else {
	            this.setElement(result(this, 'el'));
	        }
	        this._ensureElClassName();
	    },

	    _setAttributes: function(attrs) {
	        if (this.svgElement) {
	            this.vel.attr(attrs);
	        } else {
	            this.$el.attr(attrs);
	        }
	    },

	    _setStyle: function(style) {
	        this.$el.css(style);
	    },

	    _createElement: function(tagName) {
	        if (this.svgElement) {
	            return document.createElementNS(V.namespace.svg, tagName);
	        } else {
	            return document.createElement(tagName);
	        }
	    },

	    // Utilize an alternative DOM manipulation API by
	    // adding an element reference wrapped in Vectorizer.
	    _setElement: function(el) {
	        this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
	        this.el = this.$el[0];
	        if (this.svgElement) { this.vel = V(this.el); }
	    },

	    _ensureElClassName: function() {
	        var className = result(this, 'className');
	        if (!className) { return; }
	        var prefixedClassName = addClassNamePrefix(className);
	        // Note: className removal here kept for backwards compatibility only
	        if (this.svgElement) {
	            this.vel.removeClass(className).addClass(prefixedClassName);
	        } else {
	            this.$el.removeClass(className).addClass(prefixedClassName);
	        }
	    },

	    init: function() {
	        // Intentionally empty.
	        // This method is meant to be overridden.
	    },

	    onRender: function() {
	        // Intentionally empty.
	        // This method is meant to be overridden.
	    },

	    confirmUpdate: function() {
	        // Intentionally empty.
	        // This method is meant to be overridden.
	        return 0;
	    },

	    setTheme: function(theme, opt) {

	        opt = opt || {};

	        // Theme is already set, override is required, and override has not been set.
	        // Don't set the theme.
	        if (this.theme && this.requireSetThemeOverride && !opt.override) {
	            return this;
	        }

	        this.removeThemeClassName();
	        this.addThemeClassName(theme);
	        this.onSetTheme(this.theme/* oldTheme */, theme/* newTheme */);
	        this.theme = theme;

	        return this;
	    },

	    addThemeClassName: function(theme) {

	        theme = theme || this.theme;
	        if (!theme) { return this; }

	        var className = this.themeClassNamePrefix + theme;

	        if (this.svgElement) {
	            this.vel.addClass(className);
	        } else {
	            this.$el.addClass(className);
	        }

	        return this;
	    },

	    removeThemeClassName: function(theme) {

	        theme = theme || this.theme;

	        var className = this.themeClassNamePrefix + theme;

	        if (this.svgElement) {
	            this.vel.removeClass(className);
	        } else {
	            this.$el.removeClass(className);
	        }

	        return this;
	    },

	    onSetTheme: function(oldTheme, newTheme) {
	        // Intentionally empty.
	        // This method is meant to be overridden.
	    },

	    remove: function() {

	        this.onRemove();
	        this.undelegateDocumentEvents();

	        views[this.cid] = null;

	        Backbone.View.prototype.remove.apply(this, arguments);

	        return this;
	    },

	    onRemove: function() {
	        // Intentionally empty.
	        // This method is meant to be overridden.
	    },

	    getEventNamespace: function() {
	        // Returns a per-session unique namespace
	        return '.joint-event-ns-' + this.cid;
	    },

	    delegateElementEvents: function(element, events, data) {
	        if (!events) { return this; }
	        data || (data = {});
	        var eventNS = this.getEventNamespace();
	        for (var eventName in events) {
	            var method = events[eventName];
	            if (typeof method !== 'function') { method = this[method]; }
	            if (!method) { continue; }
	            $(element).on(eventName + eventNS, data, method.bind(this));
	        }
	        return this;
	    },

	    undelegateElementEvents: function(element) {
	        $(element).off(this.getEventNamespace());
	        return this;
	    },

	    delegateDocumentEvents: function(events, data) {
	        events || (events = result(this, 'documentEvents'));
	        return this.delegateElementEvents(document, events, data);
	    },

	    undelegateDocumentEvents: function() {
	        return this.undelegateElementEvents(document);
	    },

	    eventData: function(evt, data) {
	        if (!evt) { throw new Error('eventData(): event object required.'); }
	        var currentData = evt.data;
	        var key = '__' + this.cid + '__';
	        if (data === undefined) {
	            if (!currentData) { return {}; }
	            return currentData[key] || {};
	        }
	        currentData || (currentData = evt.data = {});
	        currentData[key] || (currentData[key] = {});
	        assign(currentData[key], data);
	        return this;
	    },

	    stopPropagation: function(evt) {
	        this.eventData(evt, { propagationStopped: true });
	        return this;
	    },

	    isPropagationStopped: function(evt) {
	        return !!this.eventData(evt).propagationStopped;
	    }

	}, {

	    extend: function() {

	        var args = Array.from(arguments);

	        // Deep clone the prototype and static properties objects.
	        // This prevents unexpected behavior where some properties are overwritten outside of this function.
	        var protoProps = args[0] && assign({}, args[0]) || {};
	        var staticProps = args[1] && assign({}, args[1]) || {};

	        // Need the real render method so that we can wrap it and call it later.
	        var renderFn = protoProps.render || (this.prototype && this.prototype.render) || null;

	        /*
	            Wrap the real render method so that:
	                .. `onRender` is always called.
	                .. `this` is always returned.
	        */
	        protoProps.render = function() {

	            if (typeof renderFn === 'function') {
	                // Call the original render method.
	                renderFn.apply(this, arguments);
	            }

	            if (this.render.__render__ === renderFn) {
	                // Should always call onRender() method.
	                // Should call it only once when renderFn is actual prototype method i.e. not the wrapper
	                this.onRender();
	            }

	            // Should always return itself.
	            return this;
	        };

	        protoProps.render.__render__ = renderFn;

	        return Backbone.View.extend.call(this, protoProps, staticProps);
	    }
	});

	var DoubleTapEventName = 'dbltap';
	if ($.event && !(DoubleTapEventName in $.event.special)) {
	    var maxDelay = config.doubleTapInterval;
	    var minDelay = 30;
	    $.event.special[DoubleTapEventName] = {
	        bindType: 'touchend',
	        delegateType: 'touchend',
	        handle: function(event) {
	            var ref;

	            var args = [], len = arguments.length - 1;
	            while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
	            var handleObj = event.handleObj;
	            var target = event.target;
	            var targetData  = $.data(target);
	            var now = new Date().getTime();
	            var delta = 'lastTouch' in targetData ? now - targetData.lastTouch : 0;
	            if (delta < maxDelay && delta > minDelay) {
	                targetData.lastTouch = null;
	                event.type = handleObj.origType;
	                // let jQuery handle the triggering of "dbltap" event handlers
	                (ref = handleObj.handler).call.apply(ref, [ this, event ].concat( args ));
	            } else {
	                targetData.lastTouch = now;
	            }
	        }
	    };
	}

	var Listener = function Listener() {
	    var callbackArguments = [], len = arguments.length;
	    while ( len-- ) callbackArguments[ len ] = arguments[ len ];

	    this.callbackArguments = callbackArguments;
	};

	Listener.prototype.listenTo = function listenTo (object, evt) {
	        var this$1 = this;
	        var args = [], len = arguments.length - 2;
	        while ( len-- > 0 ) args[ len ] = arguments[ len + 2 ];

	    var ref = this;
	        var callbackArguments = ref.callbackArguments;
	    // signature 1 - (object, eventHashMap, context)
	    if (V.isObject(evt)) {
	        var context = args[0]; if ( context === void 0 ) context = null;
	        Object.entries(evt).forEach(function (ref) {
	                var eventName = ref[0];
	                var cb = ref[1];

	            if (typeof cb !== 'function') { return; }
	            // Invoke the callback with callbackArguments passed first
	            if (context || callbackArguments.length > 0) { cb = cb.bind.apply(cb, [ context ].concat( callbackArguments )); }
	            Backbone.Events.listenTo.call(this$1, object, eventName, cb);
	        });
	    }
	    // signature 2 - (object, event, callback, context)
	    else if (typeof evt === 'string' && typeof args[0] === 'function') {
	        var cb = args[0];
	            var context$1 = args[1]; if ( context$1 === void 0 ) context$1 = null;
	        // Invoke the callback with callbackArguments passed first
	        if (context$1 || callbackArguments.length > 0) { cb = cb.bind.apply(cb, [ context$1 ].concat( callbackArguments )); }
	        Backbone.Events.listenTo.call(this, object, evt, cb);
	    }
	};

	Listener.prototype.stopListening = function stopListening () {
	    Backbone.Events.stopListening.call(this);
	};



	var index$1 = ({
		views: views,
		View: View,
		Listener: Listener
	});

	var LayersNames = {
	    CELLS: 'cells',
	    BACK: 'back',
	    FRONT: 'front',
	    TOOLS: 'tools',
	    LABELS: 'labels'
	};

	var PaperLayer = View.extend({

	    tagName: 'g',
	    svgElement: true,
	    pivotNodes: null,
	    defaultTheme: null,

	    options: {
	        name: ''
	    },

	    className: function() {
	        return addClassNamePrefix(((this.options.name) + "-layer"));
	    },

	    init: function() {
	        this.pivotNodes = {};
	    },

	    insertSortedNode: function(node, z) {
	        this.el.insertBefore(node, this.insertPivot(z));
	    },

	    insertNode: function(node) {
	        var ref = this;
	        var el = ref.el;
	        if (node.parentNode !== el) {
	            el.appendChild(node);
	        }
	    },

	    insertPivot: function(z) {
	        var ref = this;
	        var el = ref.el;
	        var pivotNodes = ref.pivotNodes;
	        z = +z;
	        z || (z = 0);
	        var pivotNode = pivotNodes[z];
	        if (pivotNode) { return pivotNode; }
	        pivotNode = pivotNodes[z] = document.createComment('z-index:' + (z + 1));
	        var neighborZ = -Infinity;
	        for (var currentZ in pivotNodes) {
	            currentZ = +currentZ;
	            if (currentZ < z && currentZ > neighborZ) {
	                neighborZ = currentZ;
	                if (neighborZ === z - 1) { continue; }
	            }
	        }
	        if (neighborZ !== -Infinity) {
	            var neighborPivot = pivotNodes[neighborZ];
	            // Insert After
	            el.insertBefore(pivotNode, neighborPivot.nextSibling);
	        } else {
	            // First Child
	            el.insertBefore(pivotNode, el.firstChild);
	        }
	        return pivotNode;
	    },

	    removePivots: function() {
	        var ref = this;
	        var el = ref.el;
	        var pivotNodes = ref.pivotNodes;
	        for (var z in pivotNodes) { el.removeChild(pivotNodes[z]); }
	        this.pivotNodes = {};
	    }

	});

	function toArray$1(obj) {
	    if (!obj) { return []; }
	    if (Array.isArray(obj)) { return obj; }
	    return [obj];
	}

	var HighlighterView = View.extend({

	    tagName: 'g',
	    svgElement: true,
	    className: 'highlight',

	    HIGHLIGHT_FLAG: 1,
	    UPDATE_PRIORITY: 3,
	    DETACHABLE: false,
	    UPDATABLE: true,
	    MOUNTABLE: true,

	    cellView: null,
	    nodeSelector: null,
	    node: null,
	    updateRequested: false,
	    transformGroup: null,
	    detachedTransformGroup: null,

	    requestUpdate: function requestUpdate(cellView, nodeSelector) {
	        var paper = cellView.paper;
	        this.cellView = cellView;
	        this.nodeSelector = nodeSelector;
	        if (paper) {
	            this.updateRequested = true;
	            paper.requestViewUpdate(this, this.HIGHLIGHT_FLAG, this.UPDATE_PRIORITY);
	        }
	    },

	    confirmUpdate: function confirmUpdate() {
	        // The cellView is now rendered/updated since it has a higher update priority.
	        this.updateRequested = false;
	        var ref = this;
	        var cellView = ref.cellView;
	        var nodeSelector = ref.nodeSelector;
	        this.update(cellView, nodeSelector);
	        this.mount();
	        this.transform();
	        return 0;
	    },

	    findNode: function findNode(cellView, nodeSelector) {
	        var assign, assign$1;

	        if ( nodeSelector === void 0 ) nodeSelector = null;
	        var el;
	        if (typeof nodeSelector === 'string') {
	            (assign = cellView.findBySelector(nodeSelector), el = assign[0]);
	        } else if (isPlainObject(nodeSelector)) {
	            var isLink = cellView.model.isLink();
	            var label = nodeSelector.label; if ( label === void 0 ) label = null;
	            var port = nodeSelector.port;
	            var selector = nodeSelector.selector;
	            if (isLink && label !== null) {
	                // Link Label Selector
	                el = cellView.findLabelNode(label, selector);
	            } else if (!isLink && port) {
	                // Element Port Selector
	                el = cellView.findPortNode(port, selector);
	            } else {
	                // Cell Selector
	                (assign$1 = cellView.findBySelector(selector), el = assign$1[0]);
	            }
	        } else if (nodeSelector) {
	            el = V.toNode(nodeSelector);
	            if (!(el instanceof SVGElement)) { el = null; }
	        }
	        return el ? el : null;
	    },

	    getNodeMatrix: function getNodeMatrix(cellView, node) {
	        var ref = this;
	        var options = ref.options;
	        var layer = options.layer;
	        var rotatableNode = cellView.rotatableNode;
	        var nodeMatrix = cellView.getNodeMatrix(node);
	        if (rotatableNode) {
	            if (layer) {
	                if (rotatableNode.contains(node)) {
	                    return nodeMatrix;
	                }
	                // The node is outside of the rotatable group.
	                // Compensate the rotation set by transformGroup.
	                return cellView.getRootRotateMatrix().inverse().multiply(nodeMatrix);
	            } else {
	                return cellView.getNodeRotateMatrix(node).multiply(nodeMatrix);
	            }
	        }
	        return nodeMatrix;
	    },

	    mount: function mount() {
	        var ref = this;
	        var MOUNTABLE = ref.MOUNTABLE;
	        var cellView = ref.cellView;
	        var el = ref.el;
	        var options = ref.options;
	        var transformGroup = ref.transformGroup;
	        var detachedTransformGroup = ref.detachedTransformGroup;
	        if (!MOUNTABLE || transformGroup) { return; }
	        var cellViewRoot = cellView.vel;
	        var paper = cellView.paper;
	        var layerName = options.layer;
	        if (layerName) {
	            var vGroup;
	            if (detachedTransformGroup) {
	                vGroup = detachedTransformGroup;
	                this.detachedTransformGroup = null;
	            } else {
	                vGroup = V('g').addClass('highlight-transform').append(el);
	            }
	            this.transformGroup = vGroup;
	            paper.getLayerView(layerName).insertSortedNode(vGroup.node, options.z);
	        } else {
	            // TODO: prepend vs append
	            if (!el.parentNode || el.nextSibling) {
	                // Not appended yet or not the last child
	                cellViewRoot.append(el);
	            }
	        }
	    },

	    unmount: function unmount() {
	        var ref = this;
	        var MOUNTABLE = ref.MOUNTABLE;
	        var transformGroup = ref.transformGroup;
	        var vel = ref.vel;
	        if (!MOUNTABLE) { return; }
	        if (transformGroup) {
	            this.transformGroup = null;
	            this.detachedTransformGroup = transformGroup;
	            transformGroup.remove();
	        } else {
	            vel.remove();
	        }
	    },

	    transform: function transform() {
	        var ref = this;
	        var transformGroup = ref.transformGroup;
	        var cellView = ref.cellView;
	        var updateRequested = ref.updateRequested;
	        if (!transformGroup || cellView.model.isLink() || updateRequested) { return; }
	        var translateMatrix = cellView.getRootTranslateMatrix();
	        var rotateMatrix = cellView.getRootRotateMatrix();
	        var transformMatrix = translateMatrix.multiply(rotateMatrix);
	        transformGroup.attr('transform', V.matrixToTransformString(transformMatrix));
	    },

	    update: function update() {
	        var ref = this;
	        var prevNode = ref.node;
	        var cellView = ref.cellView;
	        var nodeSelector = ref.nodeSelector;
	        var updateRequested = ref.updateRequested;
	        var id = ref.id;
	        if (updateRequested) { return; }
	        var node = this.node = this.findNode(cellView, nodeSelector);
	        if (prevNode) {
	            this.unhighlight(cellView, prevNode);
	        }
	        if (node) {
	            this.highlight(cellView, node);
	            this.mount();
	        } else {
	            this.unmount();
	            cellView.notify('cell:highlight:invalid', id, this);
	        }
	    },

	    onRemove: function onRemove() {
	        var ref = this;
	        var node = ref.node;
	        var cellView = ref.cellView;
	        var id = ref.id;
	        var constructor = ref.constructor;
	        if (node) {
	            this.unhighlight(cellView, node);
	        }
	        this.unmount();
	        constructor._removeRef(cellView, id);
	    },

	    highlight: function highlight(_cellView, _node) {
	        // to be overridden
	    },

	    unhighlight: function unhighlight(_cellView, _node) {
	        // to be overridden
	    },

	    // Update Attributes

	    listenToUpdateAttributes: function listenToUpdateAttributes(cellView) {
	        var attributes = result(this, 'UPDATE_ATTRIBUTES');
	        if (!Array.isArray(attributes) || attributes.length === 0) { return; }
	        this.listenTo(cellView.model, 'change', this.onCellAttributeChange);
	    },

	    onCellAttributeChange: function onCellAttributeChange() {
	        var ref = this;
	        var cellView = ref.cellView;
	        if (!cellView) { return; }
	        var model = cellView.model;
	        var paper = cellView.paper;
	        var attributes = result(this, 'UPDATE_ATTRIBUTES');
	        if (!attributes.some(function (attribute) { return model.hasChanged(attribute); })) { return; }
	        paper.requestViewUpdate(this, this.HIGHLIGHT_FLAG, this.UPDATE_PRIORITY);
	    }

	}, {

	    _views: {},

	    // Used internally by CellView highlight()
	    highlight: function(cellView, node, opt) {
	        var id = this.uniqueId(node, opt);
	        this.add(cellView, node, id, opt);
	    },

	    // Used internally by CellView unhighlight()
	    unhighlight: function(cellView, node, opt) {
	        var id = this.uniqueId(node, opt);
	        this.remove(cellView, id);
	    },

	    get: function get(cellView, id) {
	        if ( id === void 0 ) id = null;

	        var cid = cellView.cid;
	        var ref$2 = this;
	        var _views = ref$2._views;
	        var refs = _views[cid];
	        if (id === null) {
	            // all highlighters
	            var views = [];
	            if (!refs) { return views; }
	            for (var hid in refs) {
	                var ref = refs[hid];
	                if (ref instanceof this) {
	                    views.push(ref);
	                }
	            }
	            return views;
	        } else {
	            // single highlighter
	            if (!refs) { return null; }
	            if (id in refs) {
	                var ref$1 = refs[id];
	                if (ref$1 instanceof this) { return ref$1; }
	            }
	            return null;
	        }
	    },

	    add: function add(cellView, nodeSelector, id, opt) {
	        if ( opt === void 0 ) opt = {};

	        if (!id) { throw new Error('dia.HighlighterView: An ID required.'); }
	        // Search the existing view amongst all the highlighters
	        var previousView = HighlighterView.get(cellView, id);
	        if (previousView) { previousView.remove(); }
	        var view = new this(opt);
	        view.id = id;
	        this._addRef(cellView, id, view);
	        view.requestUpdate(cellView, nodeSelector);
	        view.listenToUpdateAttributes(cellView);
	        return view;
	    },

	    _addRef: function _addRef(cellView, id, view) {
	        var cid = cellView.cid;
	        var ref = this;
	        var _views = ref._views;
	        var refs = _views[cid];
	        if (!refs) { refs = _views[cid] = {}; }
	        refs[id] = view;
	    },

	    _removeRef: function _removeRef(cellView, id) {
	        var cid = cellView.cid;
	        var ref = this;
	        var _views = ref._views;
	        var refs = _views[cid];
	        if (!refs) { return; }
	        if (id) { delete refs[id]; }
	        for (var _ in refs) { return; }
	        delete _views[cid];
	    },

	    remove: function remove(cellView, id) {
	        if ( id === void 0 ) id = null;

	        toArray$1(this.get(cellView, id)).forEach(function (view) {
	            view.remove();
	        });
	    },

	    removeAll: function removeAll(paper, id) {
	        if ( id === void 0 ) id = null;

	        var ref = this;
	        var _views = ref._views;

	        for (var cid in _views) {
	            for (var hid in _views[cid]) {
	                var view = _views[cid][hid];

	                if (view.cellView.paper === paper && view instanceof this && (id === null || hid === id)) {
	                    view.remove();
	                }
	            }
	        }
	    },

	    update: function update(cellView, id, dirty) {
	        if ( id === void 0 ) id = null;
	        if ( dirty === void 0 ) dirty = false;

	        toArray$1(this.get(cellView, id)).forEach(function (view) {
	            if (dirty || view.UPDATABLE) { view.update(); }
	        });
	    },

	    transform: function transform(cellView, id) {
	        if ( id === void 0 ) id = null;

	        toArray$1(this.get(cellView, id)).forEach(function (view) {
	            if (view.UPDATABLE) { view.transform(); }
	        });
	    },

	    unmount: function unmount(cellView, id) {
	        if ( id === void 0 ) id = null;

	        toArray$1(this.get(cellView, id)).forEach(function (view) { return view.unmount(); });
	    },

	    mount: function mount(cellView, id) {
	        if ( id === void 0 ) id = null;

	        toArray$1(this.get(cellView, id)).forEach(function (view) { return view.mount(); });
	    },

	    uniqueId: function uniqueId(node, opt) {
	        if ( opt === void 0 ) opt = '';

	        return V.ensureId(node) + JSON.stringify(opt);
	    }

	});

	var HighlightingTypes = {
	    DEFAULT: 'default',
	    EMBEDDING: 'embedding',
	    CONNECTING: 'connecting',
	    MAGNET_AVAILABILITY: 'magnetAvailability',
	    ELEMENT_AVAILABILITY: 'elementAvailability'
	};

	var Flags = {
	    TOOLS: 'TOOLS',
	};

	// CellView base view and controller.
	// --------------------------------------------

	// This is the base view and controller for `ElementView` and `LinkView`.
	var CellView = View.extend({

	    tagName: 'g',

	    svgElement: true,

	    selector: 'root',

	    metrics: null,

	    className: function() {

	        var classNames = ['cell'];
	        var type = this.model.get('type');

	        if (type) {

	            type.toLowerCase().split('.').forEach(function(value, index, list) {
	                classNames.push('type-' + list.slice(0, index + 1).join('-'));
	            });
	        }

	        return classNames.join(' ');
	    },

	    _presentationAttributes: null,
	    _flags: null,

	    setFlags: function() {
	        var flags = {};
	        var attributes = {};
	        var shift = 0;
	        var i, n, label;
	        var presentationAttributes = result(this, 'presentationAttributes');
	        for (var attribute in presentationAttributes) {
	            if (!presentationAttributes.hasOwnProperty(attribute)) { continue; }
	            var labels = presentationAttributes[attribute];
	            if (!Array.isArray(labels)) { labels = [labels]; }
	            for (i = 0, n = labels.length; i < n; i++) {
	                label = labels[i];
	                var flag = flags[label];
	                if (!flag) {
	                    flag = flags[label] = 1<<(shift++);
	                }
	                attributes[attribute] |= flag;
	            }
	        }
	        var initFlag = result(this, 'initFlag');
	        if (!Array.isArray(initFlag)) { initFlag = [initFlag]; }
	        for (i = 0, n = initFlag.length; i < n; i++) {
	            label = initFlag[i];
	            if (!flags[label]) { flags[label] = 1<<(shift++); }
	        }

	        // 26 - 30 are reserved for paper flags
	        // 31+ overflows maximal number
	        if (shift > 25) { throw new Error('dia.CellView: Maximum number of flags exceeded.'); }

	        this._flags = flags;
	        this._presentationAttributes = attributes;
	    },

	    hasFlag: function(flag, label) {
	        return flag & this.getFlag(label);
	    },

	    removeFlag: function(flag, label) {
	        return flag ^ (flag & this.getFlag(label));
	    },

	    getFlag: function(label) {
	        var flags = this._flags;
	        if (!flags) { return 0; }
	        var flag = 0;
	        if (Array.isArray(label)) {
	            for (var i = 0, n = label.length; i < n; i++) { flag |= flags[label[i]]; }
	        } else {
	            flag |= flags[label];
	        }
	        return flag;
	    },

	    attributes: function() {
	        var cell = this.model;
	        return {
	            'model-id': cell.id,
	            'data-type': cell.attributes.type
	        };
	    },

	    constructor: function(options) {

	        // Make sure a global unique id is assigned to this view. Store this id also to the properties object.
	        // The global unique id makes sure that the same view can be rendered on e.g. different machines and
	        // still be associated to the same object among all those clients. This is necessary for real-time
	        // collaboration mechanism.
	        options.id = options.id || guid(this);

	        View.call(this, options);
	    },

	    initialize: function() {

	        this.setFlags();

	        View.prototype.initialize.apply(this, arguments);

	        this.cleanNodesCache();

	        // Store reference to this to the <g> DOM element so that the view is accessible through the DOM tree.
	        this.$el.data('view', this);

	        this.startListening();
	    },

	    startListening: function() {
	        this.listenTo(this.model, 'change', this.onAttributesChange);
	    },

	    onAttributesChange: function(model, opt) {
	        var flag = model.getChangeFlag(this._presentationAttributes);
	        if (opt.updateHandled || !flag) { return; }
	        if (opt.dirty && this.hasFlag(flag, 'UPDATE')) { flag |= this.getFlag('RENDER'); }
	        // TODO: tool changes does not need to be sync
	        // Fix Segments tools
	        if (opt.tool) { opt.async = false; }
	        this.requestUpdate(flag, opt);
	    },

	    requestUpdate: function(flags, opt) {
	        var ref = this;
	        var paper = ref.paper;
	        if (paper && flags > 0) {
	            paper.requestViewUpdate(this, flags, this.UPDATE_PRIORITY, opt);
	        }
	    },

	    parseDOMJSON: function(markup, root) {

	        var doc = parseDOMJSON(markup);
	        var selectors = doc.selectors;
	        var groups = doc.groupSelectors;
	        for (var group in groups) {
	            if (selectors[group]) { throw new Error('dia.CellView: ambiguous group selector'); }
	            selectors[group] = groups[group];
	        }
	        if (root) {
	            var rootSelector = this.selector;
	            if (selectors[rootSelector]) { throw new Error('dia.CellView: ambiguous root selector.'); }
	            selectors[rootSelector] = root;
	        }
	        return { fragment: doc.fragment, selectors: selectors };
	    },

	    // Return `true` if cell link is allowed to perform a certain UI `feature`.
	    // Example: `can('vertexMove')`, `can('labelMove')`.
	    can: function(feature) {

	        var interactive = isFunction(this.options.interactive)
	            ? this.options.interactive(this)
	            : this.options.interactive;

	        return (isObject$1(interactive) && interactive[feature] !== false) ||
	            (isBoolean(interactive) && interactive !== false);
	    },

	    findBySelector: function(selector, root, selectors) {

	        root || (root = this.el);
	        selectors || (selectors = this.selectors);

	        // These are either descendants of `this.$el` of `this.$el` itself.
	        // `.` is a special selector used to select the wrapping `<g>` element.
	        if (!selector || selector === '.') { return [root]; }
	        if (selectors) {
	            var nodes = selectors[selector];
	            if (nodes) {
	                if (Array.isArray(nodes)) { return nodes; }
	                return [nodes];
	            }
	        }

	        // Maintaining backwards compatibility
	        // e.g. `circle:first` would fail with querySelector() call
	        if (config.useCSSSelectors) { return $(root).find(selector).toArray(); }

	        return [];
	    },

	    notify: function(eventName) {

	        if (this.paper) {

	            var args = Array.prototype.slice.call(arguments, 1);

	            // Trigger the event on both the element itself and also on the paper.
	            this.trigger.apply(this, [eventName].concat(args));

	            // Paper event handlers receive the view object as the first argument.
	            this.paper.trigger.apply(this.paper, [eventName, this].concat(args));
	        }
	    },

	    getBBox: function(opt) {

	        var bbox;
	        if (opt && opt.useModelGeometry) {
	            var model = this.model;
	            bbox = model.getBBox().bbox(model.angle());
	        } else {
	            bbox = this.getNodeBBox(this.el);
	        }

	        return this.paper.localToPaperRect(bbox);
	    },

	    getNodeBBox: function(magnet) {

	        var rect = this.getNodeBoundingRect(magnet);
	        var transformMatrix = this.getRootTranslateMatrix().multiply(this.getNodeRotateMatrix(magnet));
	        var magnetMatrix = this.getNodeMatrix(magnet);
	        return V.transformRect(rect, transformMatrix.multiply(magnetMatrix));
	    },

	    getNodeRotateMatrix: function getNodeRotateMatrix(node) {
	        if (!this.rotatableNode || this.rotatableNode.contains(node)) {
	            // Rotate transformation is applied to all nodes when no rotatableGroup
	            // is present or to nodes inside the rotatableGroup only.
	            return this.getRootRotateMatrix();
	        }
	        // Nodes outside the rotatable group
	        return V.createSVGMatrix();
	    },

	    getNodeUnrotatedBBox: function(magnet) {

	        var rect = this.getNodeBoundingRect(magnet);
	        var magnetMatrix = this.getNodeMatrix(magnet);
	        var translateMatrix = this.getRootTranslateMatrix();
	        return V.transformRect(rect, translateMatrix.multiply(magnetMatrix));
	    },

	    getRootTranslateMatrix: function() {

	        var model = this.model;
	        var position = model.position();
	        var mt = V.createSVGMatrix().translate(position.x, position.y);
	        return mt;
	    },

	    getRootRotateMatrix: function() {

	        var mr = V.createSVGMatrix();
	        var model = this.model;
	        var angle = model.angle();
	        if (angle) {
	            var bbox = model.getBBox();
	            var cx = bbox.width / 2;
	            var cy = bbox.height / 2;
	            mr = mr.translate(cx, cy).rotate(angle).translate(-cx, -cy);
	        }
	        return mr;
	    },

	    _notifyHighlight: function(eventName, el, opt) {
	        var assign, assign$1;

	        if ( opt === void 0 ) opt = {};
	        var ref = this;
	        var rootNode = ref.el;
	        var node;
	        if (typeof el === 'string') {
	            (assign = this.findBySelector(el), node = assign[0], node = node === void 0 ? rootNode : node);
	        } else {
	            (assign$1 = this.$(el), node = assign$1[0], node = node === void 0 ? rootNode : node);
	        }
	        // set partial flag if the highlighted element is not the entire view.
	        opt.partial = (node !== rootNode);
	        // translate type flag into a type string
	        if (opt.type === undefined) {
	            var type;
	            switch (true) {
	                case opt.embedding:
	                    type = HighlightingTypes.EMBEDDING;
	                    break;
	                case opt.connecting:
	                    type = HighlightingTypes.CONNECTING;
	                    break;
	                case opt.magnetAvailability:
	                    type = HighlightingTypes.MAGNET_AVAILABILITY;
	                    break;
	                case opt.elementAvailability:
	                    type = HighlightingTypes.ELEMENT_AVAILABILITY;
	                    break;
	                default:
	                    type = HighlightingTypes.DEFAULT;
	                    break;
	            }
	            opt.type = type;
	        }
	        this.notify(eventName, node, opt);
	        return this;
	    },

	    highlight: function(el, opt) {
	        return this._notifyHighlight('cell:highlight', el, opt);
	    },

	    unhighlight: function(el, opt) {
	        if ( opt === void 0 ) opt = {};

	        return this._notifyHighlight('cell:unhighlight', el, opt);
	    },

	    // Find the closest element that has the `magnet` attribute set to `true`. If there was not such
	    // an element found, return the root element of the cell view.
	    findMagnet: function(el) {

	        var root = this.el;
	        var magnet = this.$(el)[0];
	        if (!magnet) {
	            magnet = root;
	        }

	        do {
	            var magnetAttribute = magnet.getAttribute('magnet');
	            var isMagnetRoot = (magnet === root);
	            if ((magnetAttribute || isMagnetRoot) && magnetAttribute !== 'false') {
	                return magnet;
	            }
	            if (isMagnetRoot) {
	                // If the overall cell has set `magnet === false`, then return `undefined` to
	                // announce there is no magnet found for this cell.
	                // This is especially useful to set on cells that have 'ports'. In this case,
	                // only the ports have set `magnet === true` and the overall element has `magnet === false`.
	                return undefined;
	            }
	            magnet = magnet.parentNode;
	        } while (magnet);

	        return undefined;
	    },

	    findProxyNode: function(el, type) {
	        el || (el = this.el);
	        var nodeSelector = el.getAttribute((type + "-selector"));
	        if (nodeSelector) {
	            var ref = this.findBySelector(nodeSelector);
	            var proxyNode = ref[0];
	            if (proxyNode) { return proxyNode; }
	        }
	        return el;
	    },

	    // Construct a unique selector for the `el` element within this view.
	    // `prevSelector` is being collected through the recursive call.
	    // No value for `prevSelector` is expected when using this method.
	    getSelector: function(el, prevSelector) {

	        var selector;

	        if (el === this.el) {
	            if (typeof prevSelector === 'string') { selector = '> ' + prevSelector; }
	            return selector;
	        }

	        if (el) {

	            var nthChild = V(el).index() + 1;
	            selector = el.tagName + ':nth-child(' + nthChild + ')';

	            if (prevSelector) {
	                selector += ' > ' + prevSelector;
	            }

	            selector = this.getSelector(el.parentNode, selector);
	        }

	        return selector;
	    },

	    addLinkFromMagnet: function(magnet, x, y) {

	        var paper = this.paper;
	        var graph = paper.model;

	        var link = paper.getDefaultLink(this, magnet);
	        link.set({
	            source: this.getLinkEnd(magnet, x, y, link, 'source'),
	            target: { x: x, y: y }
	        }).addTo(graph, {
	            async: false,
	            ui: true
	        });

	        return link.findView(paper);
	    },

	    getLinkEnd: function(magnet) {
	        var ref;

	        var args = [], len = arguments.length - 1;
	        while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];

	        var model = this.model;
	        var id = model.id;
	        var port = this.findAttribute('port', magnet);
	        // Find a unique `selector` of the element under pointer that is a magnet.
	        var selector = magnet.getAttribute('joint-selector');

	        var end = { id: id };
	        if (selector != null) { end.magnet = selector; }
	        if (port != null) {
	            end.port = port;
	            if (!model.hasPort(port) && !selector) {
	                // port created via the `port` attribute (not API)
	                end.selector = this.getSelector(magnet);
	            }
	        } else if (selector == null && this.el !== magnet) {
	            end.selector = this.getSelector(magnet);
	        }

	        return (ref = this).customizeLinkEnd.apply(ref, [ end, magnet ].concat( args ));
	    },

	    customizeLinkEnd: function(end, magnet, x, y, link, endType) {
	        var ref = this;
	        var paper = ref.paper;
	        var ref$1 = paper.options;
	        var connectionStrategy = ref$1.connectionStrategy;
	        if (typeof connectionStrategy === 'function') {
	            var strategy = connectionStrategy.call(paper, end, this, magnet, new Point(x, y), link, endType, paper);
	            if (strategy) { return strategy; }
	        }
	        return end;
	    },

	    getMagnetFromLinkEnd: function(end) {

	        var root = this.el;
	        var port = end.port;
	        var selector = end.magnet;
	        var model = this.model;
	        var magnet;
	        if (port != null && model.isElement() && model.hasPort(port)) {
	            magnet = this.findPortNode(port, selector) || root;
	        } else {
	            if (!selector) { selector = end.selector; }
	            if (!selector && port != null) {
	                // link end has only `id` and `port` property referencing
	                // a port created via the `port` attribute (not API).
	                selector = '[port="' + port + '"]';
	            }
	            magnet = this.findBySelector(selector, root, this.selectors)[0];
	        }

	        return this.findProxyNode(magnet, 'magnet');
	    },

	    dragLinkStart: function(evt, magnet, x, y) {
	        this.model.startBatch('add-link');
	        var linkView = this.addLinkFromMagnet(magnet, x, y);
	        // backwards compatibility events
	        linkView.notifyPointerdown(evt, x, y);
	        linkView.eventData(evt, linkView.startArrowheadMove('target', { whenNotAllowed: 'remove' }));
	        this.eventData(evt, { linkView: linkView });
	    },

	    dragLink: function(evt, x, y) {
	        var data = this.eventData(evt);
	        var linkView = data.linkView;
	        if (linkView) {
	            linkView.pointermove(evt, x, y);
	        } else {
	            var paper = this.paper;
	            var magnetThreshold = paper.options.magnetThreshold;
	            var currentTarget = this.getEventTarget(evt);
	            var targetMagnet = data.targetMagnet;
	            if (magnetThreshold === 'onleave') {
	                // magnetThreshold when the pointer leaves the magnet
	                if (targetMagnet === currentTarget || V(targetMagnet).contains(currentTarget)) { return; }
	            } else {
	                // magnetThreshold defined as a number of movements
	                if (paper.eventData(evt).mousemoved <= magnetThreshold) { return; }
	            }
	            this.dragLinkStart(evt, targetMagnet, x, y);
	        }
	    },

	    dragLinkEnd: function(evt, x, y) {
	        var data = this.eventData(evt);
	        var linkView = data.linkView;
	        if (!linkView) { return; }
	        linkView.pointerup(evt, x, y);
	        this.model.stopBatch('add-link');
	    },

	    getAttributeDefinition: function(attrName) {

	        return this.model.constructor.getAttributeDefinition(attrName);
	    },

	    setNodeAttributes: function(node, attrs) {

	        if (!isEmpty(attrs)) {
	            if (node instanceof SVGElement) {
	                V(node).attr(attrs);
	            } else {
	                $(node).attr(attrs);
	            }
	        }
	    },

	    processNodeAttributes: function(node, attrs) {

	        var attrName, attrVal, def, i, n;
	        var normalAttrs, setAttrs, positionAttrs, offsetAttrs;
	        var relatives = [];
	        // divide the attributes between normal and special
	        for (attrName in attrs) {
	            if (!attrs.hasOwnProperty(attrName)) { continue; }
	            attrVal = attrs[attrName];
	            def = this.getAttributeDefinition(attrName);
	            if (def && (!isFunction(def.qualify) || def.qualify.call(this, attrVal, node, attrs, this))) {
	                if (isString(def.set)) {
	                    normalAttrs || (normalAttrs = {});
	                    normalAttrs[def.set] = attrVal;
	                }
	                if (attrVal !== null) {
	                    relatives.push(attrName, def);
	                }
	            } else {
	                normalAttrs || (normalAttrs = {});
	                normalAttrs[toKebabCase(attrName)] = attrVal;
	            }
	        }

	        // handle the rest of attributes via related method
	        // from the special attributes namespace.
	        for (i = 0, n = relatives.length; i < n; i+=2) {
	            attrName = relatives[i];
	            def = relatives[i+1];
	            attrVal = attrs[attrName];
	            if (isFunction(def.set)) {
	                setAttrs || (setAttrs = {});
	                setAttrs[attrName] = attrVal;
	            }
	            if (isFunction(def.position)) {
	                positionAttrs || (positionAttrs = {});
	                positionAttrs[attrName] = attrVal;
	            }
	            if (isFunction(def.offset)) {
	                offsetAttrs || (offsetAttrs = {});
	                offsetAttrs[attrName] = attrVal;
	            }
	        }

	        return {
	            raw: attrs,
	            normal: normalAttrs,
	            set: setAttrs,
	            position: positionAttrs,
	            offset: offsetAttrs
	        };
	    },

	    updateRelativeAttributes: function(node, attrs, refBBox, opt) {

	        opt || (opt = {});

	        var attrName, attrVal, def;
	        var rawAttrs = attrs.raw || {};
	        var nodeAttrs = attrs.normal || {};
	        var setAttrs = attrs.set;
	        var positionAttrs = attrs.position;
	        var offsetAttrs = attrs.offset;

	        for (attrName in setAttrs) {
	            attrVal = setAttrs[attrName];
	            def = this.getAttributeDefinition(attrName);
	            // SET - set function should return attributes to be set on the node,
	            // which will affect the node dimensions based on the reference bounding
	            // box. e.g. `width`, `height`, `d`, `rx`, `ry`, `points
	            var setResult = def.set.call(this, attrVal, refBBox.clone(), node, rawAttrs, this);
	            if (isObject$1(setResult)) {
	                assign(nodeAttrs, setResult);
	            } else if (setResult !== undefined) {
	                nodeAttrs[attrName] = setResult;
	            }
	        }

	        if (node instanceof HTMLElement) {
	            // TODO: setting the `transform` attribute on HTMLElements
	            // via `node.style.transform = 'matrix(...)';` would introduce
	            // a breaking change (e.g. basic.TextBlock).
	            this.setNodeAttributes(node, nodeAttrs);
	            return;
	        }

	        // The final translation of the subelement.
	        var nodeTransform = nodeAttrs.transform;
	        var nodeMatrix = V.transformStringToMatrix(nodeTransform);
	        var nodePosition = Point(nodeMatrix.e, nodeMatrix.f);
	        if (nodeTransform) {
	            nodeAttrs = omit(nodeAttrs, 'transform');
	            nodeMatrix.e = nodeMatrix.f = 0;
	        }

	        // Calculate node scale determined by the scalable group
	        // only if later needed.
	        var sx, sy, translation;
	        if (positionAttrs || offsetAttrs) {
	            var nodeScale = this.getNodeScale(node, opt.scalableNode);
	            sx = nodeScale.sx;
	            sy = nodeScale.sy;
	        }

	        var positioned = false;
	        for (attrName in positionAttrs) {
	            attrVal = positionAttrs[attrName];
	            def = this.getAttributeDefinition(attrName);
	            // POSITION - position function should return a point from the
	            // reference bounding box. The default position of the node is x:0, y:0 of
	            // the reference bounding box or could be further specify by some
	            // SVG attributes e.g. `x`, `y`
	            translation = def.position.call(this, attrVal, refBBox.clone(), node, rawAttrs, this);
	            if (translation) {
	                nodePosition.offset(Point(translation).scale(sx, sy));
	                positioned || (positioned = true);
	            }
	        }

	        // The node bounding box could depend on the `size` set from the previous loop.
	        // Here we know, that all the size attributes have been already set.
	        this.setNodeAttributes(node, nodeAttrs);

	        var offseted = false;
	        if (offsetAttrs) {
	            // Check if the node is visible
	            var nodeBoundingRect = this.getNodeBoundingRect(node);
	            if (nodeBoundingRect.width > 0 && nodeBoundingRect.height > 0) {
	                var nodeBBox = V.transformRect(nodeBoundingRect, nodeMatrix).scale(1 / sx, 1 / sy);
	                for (attrName in offsetAttrs) {
	                    attrVal = offsetAttrs[attrName];
	                    def = this.getAttributeDefinition(attrName);
	                    // OFFSET - offset function should return a point from the element
	                    // bounding box. The default offset point is x:0, y:0 (origin) or could be further
	                    // specify with some SVG attributes e.g. `text-anchor`, `cx`, `cy`
	                    translation = def.offset.call(this, attrVal, nodeBBox, node, rawAttrs, this);
	                    if (translation) {
	                        nodePosition.offset(Point(translation).scale(sx, sy));
	                        offseted || (offseted = true);
	                    }
	                }
	            }
	        }

	        // Do not touch node's transform attribute if there is no transformation applied.
	        if (nodeTransform !== undefined || positioned || offseted) {
	            // Round the coordinates to 1 decimal point.
	            nodePosition.round(1);
	            nodeMatrix.e = nodePosition.x;
	            nodeMatrix.f = nodePosition.y;
	            node.setAttribute('transform', V.matrixToTransformString(nodeMatrix));
	            // TODO: store nodeMatrix metrics?
	        }
	    },

	    getNodeScale: function(node, scalableNode) {

	        // Check if the node is a descendant of the scalable group.
	        var sx, sy;
	        if (scalableNode && scalableNode.contains(node)) {
	            var scale = scalableNode.scale();
	            sx = 1 / scale.sx;
	            sy = 1 / scale.sy;
	        } else {
	            sx = 1;
	            sy = 1;
	        }

	        return { sx: sx, sy: sy };
	    },

	    cleanNodesCache: function() {
	        this.metrics = {};
	    },

	    nodeCache: function(magnet) {

	        var metrics = this.metrics;
	        // Don't use cache? It most likely a custom view with overridden update.
	        if (!metrics) { return {}; }
	        var id = V.ensureId(magnet);
	        var value = metrics[id];
	        if (!value) { value = metrics[id] = {}; }
	        return value;
	    },

	    getNodeData: function(magnet) {

	        var metrics = this.nodeCache(magnet);
	        if (!metrics.data) { metrics.data = {}; }
	        return metrics.data;
	    },

	    getNodeBoundingRect: function(magnet) {

	        var metrics = this.nodeCache(magnet);
	        if (metrics.boundingRect === undefined) { metrics.boundingRect = V(magnet).getBBox(); }
	        return new Rect(metrics.boundingRect);
	    },

	    getNodeMatrix: function(magnet) {

	        var metrics = this.nodeCache(magnet);
	        if (metrics.magnetMatrix === undefined) {
	            var ref = this;
	            var rotatableNode = ref.rotatableNode;
	            var el = ref.el;
	            var target;
	            if (rotatableNode && rotatableNode.contains(magnet)) {
	                target = rotatableNode;
	            } else {
	                target = el;
	            }
	            metrics.magnetMatrix = V(magnet).getTransformToElement(target);
	        }
	        return V.createSVGMatrix(metrics.magnetMatrix);
	    },

	    getNodeShape: function(magnet) {

	        var metrics = this.nodeCache(magnet);
	        if (metrics.geometryShape === undefined) { metrics.geometryShape = V(magnet).toGeometryShape(); }
	        return metrics.geometryShape.clone();
	    },

	    isNodeConnection: function(node) {
	        return this.model.isLink() && (!node || node === this.el);
	    },

	    findNodesAttributes: function(attrs, root, selectorCache, selectors) {

	        var i, n, nodeAttrs, nodeId;
	        var nodesAttrs = {};
	        var mergeIds = [];
	        for (var selector in attrs) {
	            if (!attrs.hasOwnProperty(selector)) { continue; }
	            nodeAttrs = attrs[selector];
	            if (!isPlainObject(nodeAttrs)) { continue; } // Not a valid selector-attributes pair
	            var selected = selectorCache[selector] = this.findBySelector(selector, root, selectors);
	            for (i = 0, n = selected.length; i < n; i++) {
	                var node = selected[i];
	                nodeId = V.ensureId(node);
	                // "unique" selectors are selectors that referencing a single node (defined by `selector`)
	                // groupSelector referencing a single node is not "unique"
	                var unique = (selectors && selectors[selector] === node);
	                var prevNodeAttrs = nodesAttrs[nodeId];
	                if (prevNodeAttrs) {
	                    // Note, that nodes referenced by deprecated `CSS selectors` are not taken into account.
	                    // e.g. css:`.circle` and selector:`circle` can be applied in a random order
	                    if (!prevNodeAttrs.array) {
	                        mergeIds.push(nodeId);
	                        prevNodeAttrs.array = true;
	                        prevNodeAttrs.attributes = [prevNodeAttrs.attributes];
	                        prevNodeAttrs.selectedLength = [prevNodeAttrs.selectedLength];
	                    }
	                    var attributes = prevNodeAttrs.attributes;
	                    var selectedLength = prevNodeAttrs.selectedLength;
	                    if (unique) {
	                        // node referenced by `selector`
	                        attributes.unshift(nodeAttrs);
	                        selectedLength.unshift(-1);
	                    } else {
	                        // node referenced by `groupSelector`
	                        var sortIndex = sortedIndex(selectedLength, n);
	                        attributes.splice(sortIndex, 0, nodeAttrs);
	                        selectedLength.splice(sortIndex, 0, n);
	                    }
	                } else {
	                    nodesAttrs[nodeId] = {
	                        attributes: nodeAttrs,
	                        selectedLength: unique ? -1 : n,
	                        node: node,
	                        array: false
	                    };
	                }
	            }
	        }

	        for (i = 0, n = mergeIds.length; i < n; i++) {
	            nodeId = mergeIds[i];
	            nodeAttrs = nodesAttrs[nodeId];
	            nodeAttrs.attributes = merge.apply(void 0, [ {} ].concat( nodeAttrs.attributes.reverse() ));
	        }

	        return nodesAttrs;
	    },

	    getEventTarget: function(evt, opt) {
	        if ( opt === void 0 ) opt = {};

	        var target = evt.target;
	        var type = evt.type;
	        var clientX = evt.clientX; if ( clientX === void 0 ) clientX = 0;
	        var clientY = evt.clientY; if ( clientY === void 0 ) clientY = 0;
	        if (
	            // Explicitly defined `fromPoint` option
	            opt.fromPoint ||
	            // Touchmove/Touchend event's target is not reflecting the element under the coordinates as mousemove does.
	            // It holds the element when a touchstart triggered.
	            type === 'touchmove' || type === 'touchend' ||
	            // Pointermove/Pointerup event with the pointer captured
	            ('pointerId' in evt && target.hasPointerCapture(evt.pointerId))
	        ) {
	            return document.elementFromPoint(clientX, clientY);
	        }

	        return target;
	    },

	    // Default is to process the `model.attributes.attrs` object and set attributes on subelements based on the selectors,
	    // unless `attrs` parameter was passed.
	    updateDOMSubtreeAttributes: function(rootNode, attrs, opt) {

	        opt || (opt = {});
	        opt.rootBBox || (opt.rootBBox = Rect());
	        opt.selectors || (opt.selectors = this.selectors); // selector collection to use

	        // Cache table for query results and bounding box calculation.
	        // Note that `selectorCache` needs to be invalidated for all
	        // `updateAttributes` calls, as the selectors might pointing
	        // to nodes designated by an attribute or elements dynamically
	        // created.
	        var selectorCache = {};
	        var bboxCache = {};
	        var relativeItems = [];
	        var relativeRefItems = [];
	        var item, node, nodeAttrs, nodeData, processedAttrs;

	        var roAttrs = opt.roAttributes;
	        var nodesAttrs = this.findNodesAttributes(roAttrs || attrs, rootNode, selectorCache, opt.selectors);
	        // `nodesAttrs` are different from all attributes, when
	        // rendering only  attributes sent to this method.
	        var nodesAllAttrs = (roAttrs)
	            ? this.findNodesAttributes(attrs, rootNode, selectorCache, opt.selectors)
	            : nodesAttrs;

	        for (var nodeId in nodesAttrs) {
	            nodeData = nodesAttrs[nodeId];
	            nodeAttrs = nodeData.attributes;
	            node = nodeData.node;
	            processedAttrs = this.processNodeAttributes(node, nodeAttrs);

	            if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset) {
	                // Set all the normal attributes right on the SVG/HTML element.
	                this.setNodeAttributes(node, processedAttrs.normal);

	            } else {

	                var nodeAllAttrs = nodesAllAttrs[nodeId] && nodesAllAttrs[nodeId].attributes;
	                var refSelector = (nodeAllAttrs && (nodeAttrs.ref === undefined))
	                    ? nodeAllAttrs.ref
	                    : nodeAttrs.ref;

	                var refNode;
	                if (refSelector) {
	                    refNode = (selectorCache[refSelector] || this.findBySelector(refSelector, rootNode, opt.selectors))[0];
	                    if (!refNode) {
	                        throw new Error('dia.CellView: "' + refSelector + '" reference does not exist.');
	                    }
	                } else {
	                    refNode = null;
	                }

	                item = {
	                    node: node,
	                    refNode: refNode,
	                    processedAttributes: processedAttrs,
	                    allAttributes: nodeAllAttrs
	                };

	                if (refNode) {
	                    // If an element in the list is positioned relative to this one, then
	                    // we want to insert this one before it in the list.
	                    var itemIndex = relativeRefItems.findIndex(function(item) {
	                        return item.refNode === node;
	                    });

	                    if (itemIndex > -1) {
	                        relativeRefItems.splice(itemIndex, 0, item);
	                    } else {
	                        relativeRefItems.push(item);
	                    }
	                } else {
	                    // A node with no ref attribute. To be updated before the nodes referencing other nodes.
	                    // The order of no-ref-items is not specified/important.
	                    relativeItems.push(item);
	                }
	            }
	        }

	        relativeItems.push.apply(relativeItems, relativeRefItems);

	        var rotatableMatrix;
	        for (var i = 0, n = relativeItems.length; i < n; i++) {
	            item = relativeItems[i];
	            node = item.node;
	            refNode = item.refNode;

	            // Find the reference element bounding box. If no reference was provided, we
	            // use the optional bounding box.
	            var vRotatable = V(opt.rotatableNode);
	            var refNodeId = refNode ? V.ensureId(refNode) : '';
	            var isRefNodeRotatable = !!vRotatable && !!refNode && vRotatable.contains(refNode);
	            var unrotatedRefBBox = bboxCache[refNodeId];
	            if (!unrotatedRefBBox) {
	                // Get the bounding box of the reference element relative to the `rotatable` `<g>` (without rotation)
	                // or to the root `<g>` element if no rotatable group present if reference node present.
	                // Uses the bounding box provided.
	                var transformationTarget = (isRefNodeRotatable) ? vRotatable : rootNode;
	                unrotatedRefBBox = bboxCache[refNodeId] = (refNode)
	                    ? V(refNode).getBBox({ target: transformationTarget })
	                    : opt.rootBBox;
	            }

	            if (roAttrs) {
	                // if there was a special attribute affecting the position amongst passed-in attributes
	                // we have to merge it with the rest of the element's attributes as they are necessary
	                // to update the position relatively (i.e `ref-x` && 'ref-dx')
	                processedAttrs = this.processNodeAttributes(node, item.allAttributes);
	                this.mergeProcessedAttributes(processedAttrs, item.processedAttributes);

	            } else {
	                processedAttrs = item.processedAttributes;
	            }

	            var refBBox = unrotatedRefBBox;
	            if (isRefNodeRotatable && !vRotatable.contains(node)) {
	                // if the referenced node is inside the rotatable group while the updated node is outside,
	                // we need to take the rotatable node transformation into account
	                if (!rotatableMatrix) { rotatableMatrix = V.transformStringToMatrix(vRotatable.attr('transform')); }
	                refBBox = V.transformRect(unrotatedRefBBox, rotatableMatrix);
	            }

	            this.updateRelativeAttributes(node, processedAttrs, refBBox, opt);
	        }
	    },

	    mergeProcessedAttributes: function(processedAttrs, roProcessedAttrs) {

	        processedAttrs.set || (processedAttrs.set = {});
	        processedAttrs.position || (processedAttrs.position = {});
	        processedAttrs.offset || (processedAttrs.offset = {});

	        assign(processedAttrs.set, roProcessedAttrs.set);
	        assign(processedAttrs.position, roProcessedAttrs.position);
	        assign(processedAttrs.offset, roProcessedAttrs.offset);

	        // Handle also the special transform property.
	        var transform = processedAttrs.normal && processedAttrs.normal.transform;
	        if (transform !== undefined && roProcessedAttrs.normal) {
	            roProcessedAttrs.normal.transform = transform;
	        }
	        processedAttrs.normal = roProcessedAttrs.normal;
	    },

	    // Lifecycle methods

	    // Called when the view is attached to the DOM,
	    // as result of `cell.addTo(graph)` being called (isInitialMount === true)
	    // or `paper.options.viewport` returning `true` (isInitialMount === false).
	    onMount: function onMount(isInitialMount) {
	        if (isInitialMount) { return; }
	        this.mountTools();
	        HighlighterView.mount(this);
	    },

	    // Called when the view is detached from the DOM,
	    // as result of `paper.options.viewport` returning `false`.
	    onDetach: function onDetach() {
	        this.unmountTools();
	        HighlighterView.unmount(this);
	    },

	    // Called when the view is removed from the DOM
	    // as result of `cell.remove()`.
	    onRemove: function() {
	        this.removeTools();
	        this.removeHighlighters();
	    },

	    _toolsView: null,

	    hasTools: function(name) {
	        var toolsView = this._toolsView;
	        if (!toolsView) { return false; }
	        if (!name) { return true; }
	        return (toolsView.getName() === name);
	    },

	    addTools: function(toolsView) {

	        this.removeTools();

	        if (toolsView) {
	            this._toolsView = toolsView;
	            toolsView.configure({ relatedView: this });
	            toolsView.listenTo(this.paper, 'tools:event', this.onToolEvent.bind(this));
	        }
	        return this;
	    },

	    unmountTools: function unmountTools() {
	        var toolsView = this._toolsView;
	        if (toolsView) { toolsView.unmount(); }
	        return this;
	    },

	    mountTools: function mountTools() {
	        var toolsView = this._toolsView;
	        // Prevent unnecessary re-appending of the tools.
	        if (toolsView && !toolsView.isMounted()) { toolsView.mount(); }
	        return this;
	    },

	    updateTools: function(opt) {

	        var toolsView = this._toolsView;
	        if (toolsView) { toolsView.update(opt); }
	        return this;
	    },

	    removeTools: function() {

	        var toolsView = this._toolsView;
	        if (toolsView) {
	            toolsView.remove();
	            this._toolsView = null;
	        }
	        return this;
	    },

	    hideTools: function() {

	        var toolsView = this._toolsView;
	        if (toolsView) { toolsView.hide(); }
	        return this;
	    },

	    showTools: function() {

	        var toolsView = this._toolsView;
	        if (toolsView) { toolsView.show(); }
	        return this;
	    },

	    onToolEvent: function(event) {
	        switch (event) {
	            case 'remove':
	                this.removeTools();
	                break;
	            case 'hide':
	                this.hideTools();
	                break;
	            case 'show':
	                this.showTools();
	                break;
	        }
	    },

	    removeHighlighters: function() {
	        HighlighterView.remove(this);
	    },

	    updateHighlighters: function(dirty) {
	        if ( dirty === void 0 ) dirty = false;

	        HighlighterView.update(this, null, dirty);
	    },

	    transformHighlighters: function() {
	        HighlighterView.transform(this);
	    },

	    // Interaction. The controller part.
	    // ---------------------------------

	    preventDefaultInteraction: function preventDefaultInteraction(evt) {
	        this.eventData(evt, { defaultInteractionPrevented: true  });
	    },

	    isDefaultInteractionPrevented: function isDefaultInteractionPrevented(evt) {
	        var ref = this.eventData(evt);
	        var defaultInteractionPrevented = ref.defaultInteractionPrevented; if ( defaultInteractionPrevented === void 0 ) defaultInteractionPrevented = false;
	        return defaultInteractionPrevented;
	    },

	    // Interaction is handled by the paper and delegated to the view in interest.
	    // `x` & `y` parameters passed to these functions represent the coordinates already snapped to the paper grid.
	    // If necessary, real coordinates can be obtained from the `evt` event object.

	    // These functions are supposed to be overridden by the views that inherit from `joint.dia.Cell`,
	    // i.e. `joint.dia.Element` and `joint.dia.Link`.

	    pointerdblclick: function(evt, x, y) {

	        this.notify('cell:pointerdblclick', evt, x, y);
	    },

	    pointerclick: function(evt, x, y) {

	        this.notify('cell:pointerclick', evt, x, y);
	    },

	    contextmenu: function(evt, x, y) {

	        this.notify('cell:contextmenu', evt, x, y);
	    },

	    pointerdown: function(evt, x, y) {

	        var ref = this;
	        var model = ref.model;
	        var graph = model.graph;
	        if (graph) {
	            model.startBatch('pointer');
	            this.eventData(evt, { graph: graph });
	        }

	        this.notify('cell:pointerdown', evt, x, y);
	    },

	    pointermove: function(evt, x, y) {

	        this.notify('cell:pointermove', evt, x, y);
	    },

	    pointerup: function(evt, x, y) {

	        var ref = this.eventData(evt);
	        var graph = ref.graph;

	        this.notify('cell:pointerup', evt, x, y);

	        if (graph) {
	            // we don't want to trigger event on model as model doesn't
	            // need to be member of collection anymore (remove)
	            graph.stopBatch('pointer', { cell: this.model });
	        }
	    },

	    mouseover: function(evt) {

	        this.notify('cell:mouseover', evt);
	    },

	    mouseout: function(evt) {

	        this.notify('cell:mouseout', evt);
	    },

	    mouseenter: function(evt) {

	        this.notify('cell:mouseenter', evt);
	    },

	    mouseleave: function(evt) {

	        this.notify('cell:mouseleave', evt);
	    },

	    mousewheel: function(evt, x, y, delta) {

	        this.notify('cell:mousewheel', evt, x, y, delta);
	    },

	    onevent: function(evt, eventName, x, y) {

	        this.notify(eventName, evt, x, y);
	    },

	    onmagnet: function() {

	        // noop
	    },

	    magnetpointerdblclick: function() {

	        // noop
	    },

	    magnetcontextmenu: function() {

	        // noop
	    },

	    checkMouseleave: function checkMouseleave(evt) {
	        var ref = this;
	        var paper = ref.paper;
	        var model = ref.model;
	        if (paper.isAsync()) {
	            // Make sure the source/target views are updated before this view.
	            // It's not 100% bulletproof (see below) but it's a good enough solution for now.
	            // The connected cells could be links as well. In that case, we would
	            // need to recursively go through all the connected links and update
	            // their source/target views as well.
	            if (model.isLink()) {
	                // The `this.sourceView` and `this.targetView` might not be updated yet.
	                // We need to find the view by the model.
	                var sourceElement = model.getSourceElement();
	                if (sourceElement) {
	                    var sourceView = paper.findViewByModel(sourceElement);
	                    if (sourceView) { paper.dumpView(sourceView); }
	                }
	                var targetElement = model.getTargetElement();
	                if (targetElement) {
	                    var targetView = paper.findViewByModel(targetElement);
	                    if (targetView) { paper.dumpView(targetView); }
	                }
	            }
	            // Do the updates of the current view synchronously now
	            paper.dumpView(this);
	        }
	        var target = this.getEventTarget(evt, { fromPoint: true });
	        var view = paper.findView(target);
	        if (view === this) { return; }
	        // Leaving the current view
	        this.mouseleave(evt);
	        if (!view) { return; }
	        // Entering another view
	        view.mouseenter(evt);
	    },

	    setInteractivity: function(value) {

	        this.options.interactive = value;
	    }
	}, {

	    Flags: Flags,

	    Highlighting: HighlightingTypes,

	    addPresentationAttributes: function(presentationAttributes) {
	        return merge({}, result(this.prototype, 'presentationAttributes'), presentationAttributes, function(a, b) {
	            if (!a || !b) { return; }
	            if (typeof a === 'string') { a = [a]; }
	            if (typeof b === 'string') { b = [b]; }
	            if (Array.isArray(a) && Array.isArray(b)) { return uniq(a.concat(b)); }
	        });
	    }
	});

	var Flags$1 = {
	    TOOLS: CellView.Flags.TOOLS,
	    UPDATE: 'UPDATE',
	    TRANSLATE: 'TRANSLATE',
	    RESIZE: 'RESIZE',
	    PORTS: 'PORTS',
	    ROTATE: 'ROTATE',
	    RENDER: 'RENDER'
	};

	var DragActions = {
	    MOVE: 'move',
	    MAGNET: 'magnet',
	};
	// Element base view and controller.
	// -------------------------------------------

	var ElementView = CellView.extend({

	    /**
	     * @abstract
	     */
	    _removePorts: function() {
	        // implemented in ports.js
	    },

	    /**
	     *
	     * @abstract
	     */
	    _renderPorts: function() {
	        // implemented in ports.js
	    },

	    className: function() {

	        var classNames = CellView.prototype.className.apply(this).split(' ');

	        classNames.push('element');

	        return classNames.join(' ');
	    },

	    initialize: function() {

	        CellView.prototype.initialize.apply(this, arguments);

	        this._initializePorts();
	    },

	    presentationAttributes: {
	        'attrs': [Flags$1.UPDATE],
	        'position': [Flags$1.TRANSLATE, Flags$1.TOOLS],
	        'size': [Flags$1.RESIZE, Flags$1.PORTS, Flags$1.TOOLS],
	        'angle': [Flags$1.ROTATE, Flags$1.TOOLS],
	        'markup': [Flags$1.RENDER],
	        'ports': [Flags$1.PORTS],
	    },

	    initFlag: [Flags$1.RENDER],

	    UPDATE_PRIORITY: 0,

	    confirmUpdate: function(flag, opt) {

	        var useCSSSelectors = config.useCSSSelectors;
	        if (this.hasFlag(flag, Flags$1.PORTS)) {
	            this._removePorts();
	            this._cleanPortsCache();
	        }
	        var transformHighlighters = false;
	        if (this.hasFlag(flag, Flags$1.RENDER)) {
	            this.render();
	            this.updateTools(opt);
	            this.updateHighlighters(true);
	            transformHighlighters = true;
	            flag = this.removeFlag(flag, [Flags$1.RENDER, Flags$1.UPDATE, Flags$1.RESIZE, Flags$1.TRANSLATE, Flags$1.ROTATE, Flags$1.PORTS, Flags$1.TOOLS]);
	        } else {
	            var updateHighlighters = false;

	            // Skip this branch if render is required
	            if (this.hasFlag(flag, Flags$1.RESIZE)) {
	                this.resize(opt);
	                updateHighlighters = true;
	                // Resize method is calling `update()` internally
	                flag = this.removeFlag(flag, [Flags$1.RESIZE, Flags$1.UPDATE]);
	                if (useCSSSelectors) {
	                    // `resize()` rendered the ports when useCSSSelectors are enabled
	                    flag = this.removeFlag(flag, Flags$1.PORTS);
	                }
	            }
	            if (this.hasFlag(flag, Flags$1.UPDATE)) {
	                this.update(this.model, null, opt);
	                flag = this.removeFlag(flag, Flags$1.UPDATE);
	                updateHighlighters = true;
	                if (useCSSSelectors) {
	                    // `update()` will render ports when useCSSSelectors are enabled
	                    flag = this.removeFlag(flag, Flags$1.PORTS);
	                }
	            }
	            if (this.hasFlag(flag, Flags$1.TRANSLATE)) {
	                this.translate();
	                flag = this.removeFlag(flag, Flags$1.TRANSLATE);
	                transformHighlighters = true;
	            }
	            if (this.hasFlag(flag, Flags$1.ROTATE)) {
	                this.rotate();
	                flag = this.removeFlag(flag, Flags$1.ROTATE);
	                transformHighlighters = true;
	            }
	            if (this.hasFlag(flag, Flags$1.PORTS)) {
	                this._renderPorts();
	                updateHighlighters = true;
	                flag = this.removeFlag(flag, Flags$1.PORTS);
	            }

	            if (updateHighlighters) {
	                this.updateHighlighters(false);
	            }
	        }

	        if (transformHighlighters) {
	            this.transformHighlighters();
	        }

	        if (this.hasFlag(flag, Flags$1.TOOLS)) {
	            this.updateTools(opt);
	            flag = this.removeFlag(flag, Flags$1.TOOLS);
	        }

	        return flag;
	    },

	    /**
	     * @abstract
	     */
	    _initializePorts: function() {

	    },

	    update: function(_, renderingOnlyAttrs) {

	        this.cleanNodesCache();

	        // When CSS selector strings are used, make sure no rule matches port nodes.
	        var useCSSSelectors = config.useCSSSelectors;
	        if (useCSSSelectors) { this._removePorts(); }

	        var model = this.model;
	        var modelAttrs = model.attr();
	        this.updateDOMSubtreeAttributes(this.el, modelAttrs, {
	            rootBBox: new Rect(model.size()),
	            selectors: this.selectors,
	            scalableNode: this.scalableNode,
	            rotatableNode: this.rotatableNode,
	            // Use rendering only attributes if they differs from the model attributes
	            roAttributes: (renderingOnlyAttrs === modelAttrs) ? null : renderingOnlyAttrs
	        });

	        if (useCSSSelectors) {
	            this._renderPorts();
	        }
	    },

	    rotatableSelector: 'rotatable',
	    scalableSelector: 'scalable',
	    scalableNode: null,
	    rotatableNode: null,

	    // `prototype.markup` is rendered by default. Set the `markup` attribute on the model if the
	    // default markup is not desirable.
	    renderMarkup: function() {

	        var element = this.model;
	        var markup = element.get('markup') || element.markup;
	        if (!markup) { throw new Error('dia.ElementView: markup required'); }
	        if (Array.isArray(markup)) { return this.renderJSONMarkup(markup); }
	        if (typeof markup === 'string') { return this.renderStringMarkup(markup); }
	        throw new Error('dia.ElementView: invalid markup');
	    },

	    renderJSONMarkup: function(markup) {

	        var doc = this.parseDOMJSON(markup, this.el);
	        var selectors = this.selectors = doc.selectors;
	        this.rotatableNode = V(selectors[this.rotatableSelector]) || null;
	        this.scalableNode = V(selectors[this.scalableSelector]) || null;
	        // Fragment
	        this.vel.append(doc.fragment);
	    },

	    renderStringMarkup: function(markup) {

	        var vel = this.vel;
	        vel.append(V(markup));
	        // Cache transformation groups
	        this.rotatableNode = vel.findOne('.rotatable');
	        this.scalableNode = vel.findOne('.scalable');

	        var selectors = this.selectors = {};
	        selectors[this.selector] = this.el;
	    },

	    render: function() {

	        this.vel.empty();
	        this.renderMarkup();
	        if (this.scalableNode) {
	            // Double update is necessary for elements with the scalable group only
	            // Note the resize() triggers the other `update`.
	            this.update();
	        }
	        this.resize();
	        if (this.rotatableNode) {
	            // Translate transformation is applied on `this.el` while the rotation transformation
	            // on `this.rotatableNode`
	            this.rotate();
	            this.translate();
	        } else {
	            this.updateTransformation();
	        }
	        if (!config.useCSSSelectors) { this._renderPorts(); }
	        return this;
	    },

	    resize: function(opt) {

	        if (this.scalableNode) { return this.sgResize(opt); }
	        if (this.model.attributes.angle) { this.rotate(); }
	        this.update();
	    },

	    translate: function() {

	        if (this.rotatableNode) { return this.rgTranslate(); }
	        this.updateTransformation();
	    },

	    rotate: function() {

	        if (this.rotatableNode) {
	            this.rgRotate();
	            // It's necessary to call the update for the nodes outside
	            // the rotatable group referencing nodes inside the group
	            this.update();
	            return;
	        }
	        this.updateTransformation();
	    },

	    updateTransformation: function() {

	        var transformation = this.getTranslateString();
	        var rotateString = this.getRotateString();
	        if (rotateString) { transformation += ' ' + rotateString; }
	        this.vel.attr('transform', transformation);
	    },

	    getTranslateString: function() {

	        var position = this.model.attributes.position;
	        return 'translate(' + position.x + ',' + position.y + ')';
	    },

	    getRotateString: function() {
	        var attributes = this.model.attributes;
	        var angle = attributes.angle;
	        if (!angle) { return null; }
	        var size = attributes.size;
	        return 'rotate(' + angle + ',' + (size.width / 2) + ',' + (size.height / 2) + ')';
	    },

	    // Rotatable & Scalable Group
	    // always slower, kept mainly for backwards compatibility

	    rgRotate: function() {

	        this.rotatableNode.attr('transform', this.getRotateString());
	    },

	    rgTranslate: function() {

	        this.vel.attr('transform', this.getTranslateString());
	    },

	    sgResize: function(opt) {

	        var model = this.model;
	        var angle = model.angle();
	        var size = model.size();
	        var scalable = this.scalableNode;

	        // Getting scalable group's bbox.
	        // Due to a bug in webkit's native SVG .getBBox implementation, the bbox of groups with path children includes the paths' control points.
	        // To work around the issue, we need to check whether there are any path elements inside the scalable group.
	        var recursive = false;
	        if (scalable.node.getElementsByTagName('path').length > 0) {
	            // If scalable has at least one descendant that is a path, we need to switch to recursive bbox calculation.
	            // If there are no path descendants, group bbox calculation works and so we can use the (faster) native function directly.
	            recursive = true;
	        }
	        var scalableBBox = scalable.getBBox({ recursive: recursive });

	        // Make sure `scalableBbox.width` and `scalableBbox.height` are not zero which can happen if the element does not have any content. By making
	        // the width/height 1, we prevent HTML errors of the type `scale(Infinity, Infinity)`.
	        var sx = (size.width / (scalableBBox.width || 1));
	        var sy = (size.height / (scalableBBox.height || 1));
	        scalable.attr('transform', 'scale(' + sx + ',' + sy + ')');

	        // Now the interesting part. The goal is to be able to store the object geometry via just `x`, `y`, `angle`, `width` and `height`
	        // Order of transformations is significant but we want to reconstruct the object always in the order:
	        // resize(), rotate(), translate() no matter of how the object was transformed. For that to work,
	        // we must adjust the `x` and `y` coordinates of the object whenever we resize it (because the origin of the
	        // rotation changes). The new `x` and `y` coordinates are computed by canceling the previous rotation
	        // around the center of the resized object (which is a different origin then the origin of the previous rotation)
	        // and getting the top-left corner of the resulting object. Then we clean up the rotation back to what it originally was.

	        // Cancel the rotation but now around a different origin, which is the center of the scaled object.
	        var rotatable = this.rotatableNode;
	        var rotation = rotatable && rotatable.attr('transform');
	        if (rotation) {

	            rotatable.attr('transform', rotation + ' rotate(' + (-angle) + ',' + (size.width / 2) + ',' + (size.height / 2) + ')');
	            var rotatableBBox = scalable.getBBox({ target: this.paper.cells });

	            // Store new x, y and perform rotate() again against the new rotation origin.
	            model.set('position', { x: rotatableBBox.x, y: rotatableBBox.y }, assign({ updateHandled: true }, opt));
	            this.translate();
	            this.rotate();
	        }

	        // Update must always be called on non-rotated element. Otherwise, relative positioning
	        // would work with wrong (rotated) bounding boxes.
	        this.update();
	    },

	    // Embedding mode methods.
	    // -----------------------

	    prepareEmbedding: function(data) {
	        if ( data === void 0 ) data = {};


	        var element = data.model || this.model;
	        var paper = data.paper || this.paper;
	        var graph = paper.model;

	        var initialZIndices = data.initialZIndices = {};
	        var embeddedCells = element.getEmbeddedCells({ deep: true });
	        var connectedLinks = graph.getConnectedLinks(element, { deep: true, includeEnclosed: true });

	        // Note: an embedded cell can be a connect link, but it's fine
	        // to iterate over the cell twice.
	        [
	            element ].concat( embeddedCells,
	            connectedLinks
	        ).forEach(function (cell) { return initialZIndices[cell.id] = cell.attributes.z; });

	        element.startBatch('to-front');

	        // Bring the model to the front with all his embeds.
	        element.toFront({ deep: true, ui: true });

	        // Note that at this point cells in the collection are not sorted by z index (it's running in the batch, see
	        // the dia.Graph._sortOnChangeZ), so we can't assume that the last cell in the collection has the highest z.
	        var maxZ = graph.getElements().reduce(function (max, cell) { return Math.max(max, cell.attributes.z || 0); }, 0);

	        // Move to front also all the inbound and outbound links that are connected
	        // to any of the element descendant. If we bring to front only embedded elements,
	        // links connected to them would stay in the background.
	        connectedLinks.forEach(function (link) {
	            if (link.attributes.z <= maxZ) {
	                link.set('z', maxZ + 1, { ui: true });
	            }
	        });

	        element.stopBatch('to-front');

	        // Before we start looking for suitable parent we remove the current one.
	        var parentId = element.parent();
	        if (parentId) {
	            var parent = graph.getCell(parentId);
	            parent.unembed(element, { ui: true });
	            data.initialParentId = parentId;
	        } else {
	            data.initialParentId = null;
	        }
	    },

	    processEmbedding: function(data, evt, x, y) {
	        if ( data === void 0 ) data = {};


	        var model = data.model || this.model;
	        var paper = data.paper || this.paper;
	        var graph = paper.model;
	        var ref = paper.options;
	        var findParentBy = ref.findParentBy;
	        var frontParentOnly = ref.frontParentOnly;
	        var validateEmbedding = ref.validateEmbedding;

	        var candidates;
	        if (isFunction(findParentBy)) {
	            candidates = toArray(findParentBy.call(graph, this, evt, x, y));
	        } else if (findParentBy === 'pointer') {
	            candidates = toArray(graph.findModelsFromPoint({ x: x, y: y }));
	        } else {
	            candidates = graph.findModelsUnderElement(model, { searchBy: findParentBy });
	        }

	        candidates = candidates.filter(function (el) {
	            return (el instanceof Cell) && (model.id !== el.id) && !el.isEmbeddedIn(model);
	        });

	        if (frontParentOnly) {
	            // pick the element with the highest `z` index
	            candidates = candidates.slice(-1);
	        }

	        var newCandidateView = null;
	        var prevCandidateView = data.candidateEmbedView;

	        // iterate over all candidates starting from the last one (has the highest z-index).
	        for (var i = candidates.length - 1; i >= 0; i--) {
	            var candidate = candidates[i];
	            if (prevCandidateView && prevCandidateView.model.id == candidate.id) {
	                // candidate remains the same
	                newCandidateView = prevCandidateView;
	                break;
	            } else {
	                var view = candidate.findView(paper);
	                if (!isFunction(validateEmbedding) || validateEmbedding.call(paper, this, view)) {
	                    // flip to the new candidate
	                    newCandidateView = view;
	                    break;
	                }
	            }
	        }

	        if (newCandidateView && newCandidateView != prevCandidateView) {
	            // A new candidate view found. Highlight the new one.
	            this.clearEmbedding(data);
	            data.candidateEmbedView = newCandidateView.highlight(
	                newCandidateView.findProxyNode(null, 'container'),
	                { embedding: true }
	            );
	        }

	        if (!newCandidateView && prevCandidateView) {
	            // No candidate view found. Unhighlight the previous candidate.
	            this.clearEmbedding(data);
	        }
	    },

	    clearEmbedding: function(data) {

	        data || (data = {});

	        var candidateView = data.candidateEmbedView;
	        if (candidateView) {
	            // No candidate view found. Unhighlight the previous candidate.
	            candidateView.unhighlight(
	                candidateView.findProxyNode(null, 'container'),
	                { embedding: true }
	            );
	            data.candidateEmbedView = null;
	        }
	    },

	    finalizeEmbedding: function(data) {
	        if ( data === void 0 ) data = {};


	        var candidateView = data.candidateEmbedView;
	        var element = data.model || this.model;
	        var paper = data.paper || this.paper;

	        if (candidateView) {

	            // We finished embedding. Candidate view is chosen to become the parent of the model.
	            candidateView.model.embed(element, { ui: true });
	            candidateView.unhighlight(candidateView.findProxyNode(null, 'container'), { embedding: true });

	            data.candidateEmbedView = null;

	        } else {

	            var ref = paper.options;
	            var validateUnembedding = ref.validateUnembedding;
	            var initialParentId = data.initialParentId;
	            // The element was originally embedded into another element.
	            // The interaction would unembed the element. Let's validate
	            // if the element can be unembedded.
	            if (
	                initialParentId &&
	                typeof validateUnembedding === 'function' &&
	                !validateUnembedding.call(paper, this)
	            ) {
	                this._disallowUnembed(data);
	                return;
	            }
	        }

	        paper.model.getConnectedLinks(element, { deep: true }).forEach(function (link) {
	            link.reparent({ ui: true });
	        });
	    },

	    _disallowUnembed: function(data) {
	        var model = data.model;
	        var whenNotAllowed = data.whenNotAllowed; if ( whenNotAllowed === void 0 ) whenNotAllowed = 'revert';
	        var element = model || this.model;
	        var paper = data.paper || this.paper;
	        var graph = paper.model;
	        switch (whenNotAllowed) {
	            case 'remove': {
	                element.remove({ ui: true });
	                break;
	            }
	            case 'revert': {
	                var initialParentId = data.initialParentId;
	                var initialPosition = data.initialPosition;
	                var initialZIndices = data.initialZIndices;
	                // Revert the element's position (and the position of its embedded cells if any)
	                if (initialPosition) {
	                    var x = initialPosition.x;
	                    var y = initialPosition.y;
	                    element.position(x, y, { deep: true, ui: true });
	                }
	                // Revert all the z-indices changed during the embedding
	                if (initialZIndices) {
	                    Object.keys(initialZIndices).forEach(function (id) {
	                        var cell = graph.getCell(id);
	                        if (cell) {
	                            cell.set('z', initialZIndices[id], { ui: true });
	                        }
	                    });
	                }
	                // Revert the original parent
	                var parent = graph.getCell(initialParentId);
	                if (parent) {
	                    parent.embed(element, { ui: true });
	                }
	                break;
	            }
	        }
	    },

	    getDelegatedView: function() {

	        var view = this;
	        var model = view.model;
	        var paper = view.paper;

	        while (view) {
	            if (model.isLink()) { break; }
	            if (!model.isEmbedded() || view.can('stopDelegation')) { return view; }
	            model = model.getParentCell();
	            view = paper.findViewByModel(model);
	        }

	        return null;
	    },

	    findProxyNode: function(el, type) {
	        el || (el = this.el);
	        var nodeSelector = el.getAttribute((type + "-selector"));
	        if (nodeSelector) {
	            var port = this.findAttribute('port', el);
	            if (port) {
	                var proxyPortNode = this.findPortNode(port, nodeSelector);
	                if (proxyPortNode) { return proxyPortNode; }
	            } else {
	                var ref = this.findBySelector(nodeSelector);
	                var proxyNode = ref[0];
	                if (proxyNode) { return proxyNode; }
	            }
	        }
	        return el;
	    },

	    // Interaction. The controller part.
	    // ---------------------------------

	    notifyPointerdown: function notifyPointerdown(evt, x, y) {
	        CellView.prototype.pointerdown.call(this, evt, x, y);
	        this.notify('element:pointerdown', evt, x, y);
	    },

	    notifyPointermove: function notifyPointermove(evt, x, y) {
	        CellView.prototype.pointermove.call(this, evt, x, y);
	        this.notify('element:pointermove', evt, x, y);
	    },

	    notifyPointerup: function notifyPointerup(evt, x, y) {
	        this.notify('element:pointerup', evt, x, y);
	        CellView.prototype.pointerup.call(this, evt, x, y);
	    },

	    pointerdblclick: function(evt, x, y) {

	        CellView.prototype.pointerdblclick.apply(this, arguments);
	        this.notify('element:pointerdblclick', evt, x, y);
	    },

	    pointerclick: function(evt, x, y) {

	        CellView.prototype.pointerclick.apply(this, arguments);
	        this.notify('element:pointerclick', evt, x, y);
	    },

	    contextmenu: function(evt, x, y) {

	        CellView.prototype.contextmenu.apply(this, arguments);
	        this.notify('element:contextmenu', evt, x, y);
	    },

	    pointerdown: function(evt, x, y) {

	        this.notifyPointerdown(evt, x, y);
	        this.dragStart(evt, x, y);
	    },

	    pointermove: function(evt, x, y) {

	        var data = this.eventData(evt);
	        var targetMagnet = data.targetMagnet;
	        var action = data.action;
	        var delegatedView = data.delegatedView;

	        if (targetMagnet) {
	            this.magnetpointermove(evt, targetMagnet, x, y);
	        }

	        switch (action) {
	            case DragActions.MAGNET:
	                this.dragMagnet(evt, x, y);
	                break;
	            case DragActions.MOVE:
	                (delegatedView || this).drag(evt, x, y);
	            // eslint: no-fallthrough=false
	            default:
	                if (data.preventPointerEvents) { break; }
	                this.notifyPointermove(evt, x, y);
	                break;
	        }

	        // Make sure the element view data is passed along.
	        // It could have been wiped out in the handlers above.
	        this.eventData(evt, data);
	    },

	    pointerup: function(evt, x, y) {

	        var data = this.eventData(evt);
	        var targetMagnet = data.targetMagnet;
	        var action = data.action;
	        var delegatedView = data.delegatedView;

	        if (targetMagnet) {
	            this.magnetpointerup(evt, targetMagnet, x, y);
	        }

	        switch (action) {
	            case DragActions.MAGNET:
	                this.dragMagnetEnd(evt, x, y);
	                break;
	            case DragActions.MOVE:
	                (delegatedView || this).dragEnd(evt, x, y);
	            // eslint: no-fallthrough=false
	            default:
	                if (data.preventPointerEvents) { break; }
	                this.notifyPointerup(evt, x, y);
	        }

	        if (targetMagnet) {
	            this.magnetpointerclick(evt, targetMagnet, x, y);
	        }

	        this.checkMouseleave(evt);
	    },

	    mouseover: function(evt) {

	        CellView.prototype.mouseover.apply(this, arguments);
	        this.notify('element:mouseover', evt);
	    },

	    mouseout: function(evt) {

	        CellView.prototype.mouseout.apply(this, arguments);
	        this.notify('element:mouseout', evt);
	    },

	    mouseenter: function(evt) {

	        CellView.prototype.mouseenter.apply(this, arguments);
	        this.notify('element:mouseenter', evt);
	    },

	    mouseleave: function(evt) {

	        CellView.prototype.mouseleave.apply(this, arguments);
	        this.notify('element:mouseleave', evt);
	    },

	    mousewheel: function(evt, x, y, delta) {

	        CellView.prototype.mousewheel.apply(this, arguments);
	        this.notify('element:mousewheel', evt, x, y, delta);
	    },

	    onmagnet: function(evt, x, y) {

	        var targetMagnet = evt.currentTarget;
	        this.magnetpointerdown(evt, targetMagnet, x, y);
	        this.eventData(evt, { targetMagnet: targetMagnet });
	        this.dragMagnetStart(evt, x, y);
	    },

	    magnetpointerdown: function(evt, magnet, x, y) {

	        this.notify('element:magnet:pointerdown', evt, magnet, x, y);
	    },

	    magnetpointermove: function(evt, magnet, x, y) {

	        this.notify('element:magnet:pointermove', evt, magnet, x, y);
	    },

	    magnetpointerup: function(evt, magnet, x, y) {

	        this.notify('element:magnet:pointerup', evt, magnet, x, y);
	    },

	    magnetpointerdblclick: function(evt, magnet, x, y) {

	        this.notify('element:magnet:pointerdblclick', evt, magnet, x, y);
	    },

	    magnetcontextmenu: function(evt, magnet, x, y) {

	        this.notify('element:magnet:contextmenu', evt, magnet, x, y);
	    },

	    // Drag Start Handlers

	    dragStart: function(evt, x, y) {

	        if (this.isDefaultInteractionPrevented(evt)) { return; }

	        var view = this.getDelegatedView();
	        if (!view || !view.can('elementMove')) { return; }

	        this.eventData(evt, {
	            action: DragActions.MOVE,
	            delegatedView: view
	        });

	        var position = view.model.position();
	        view.eventData(evt, {
	            initialPosition: position,
	            pointerOffset: position.difference(x, y),
	            restrictedArea: this.paper.getRestrictedArea(view, x, y)
	        });
	    },

	    dragMagnetStart: function(evt, x, y) {

	        var ref = this;
	        var paper = ref.paper;
	        var isPropagationAlreadyStopped = evt.isPropagationStopped();
	        if (isPropagationAlreadyStopped) {
	            // Special case when the propagation was already stopped
	            // on the `element:magnet:pointerdown` event.
	            // Do not trigger any `element:pointer*` events
	            // but still start the magnet dragging.
	            this.eventData(evt, { preventPointerEvents: true });
	        }

	        if (this.isDefaultInteractionPrevented(evt) || !this.can('addLinkFromMagnet')) {
	            // Stop the default action, which is to start dragging a link.
	            return;
	        }

	        var ref$1 = this.eventData(evt);
	        var targetMagnet = ref$1.targetMagnet; if ( targetMagnet === void 0 ) targetMagnet = evt.currentTarget;
	        evt.stopPropagation();

	        // Invalid (Passive) magnet. Start dragging the element.
	        if (!paper.options.validateMagnet.call(paper, this, targetMagnet, evt)) {
	            if (isPropagationAlreadyStopped) {
	                // Do not trigger `element:pointerdown` and start element dragging
	                // if the propagation was stopped.
	                this.dragStart(evt, x, y);
	                // The `element:pointerdown` event is not triggered because
	                // of `preventPointerEvents` flag.
	            } else {
	                // We need to reset the action
	                // to `MOVE` so that the element is dragged.
	                this.pointerdown(evt, x, y);
	            }
	            return;
	        }

	        // Valid magnet. Start dragging a link.
	        if (paper.options.magnetThreshold <= 0) {
	            this.dragLinkStart(evt, targetMagnet, x, y);
	        }
	        this.eventData(evt, { action: DragActions.MAGNET });
	    },

	    // Drag Handlers

	    drag: function(evt, x, y) {

	        var paper = this.paper;
	        var grid = paper.options.gridSize;
	        var element = this.model;
	        var data = this.eventData(evt);
	        var pointerOffset = data.pointerOffset;
	        var restrictedArea = data.restrictedArea;
	        var embedding = data.embedding;

	        // Make sure the new element's position always snaps to the current grid
	        var elX = snapToGrid(x + pointerOffset.x, grid);
	        var elY = snapToGrid(y + pointerOffset.y, grid);

	        element.position(elX, elY, { restrictedArea: restrictedArea, deep: true, ui: true });

	        if (paper.options.embeddingMode) {
	            if (!embedding) {
	                // Prepare the element for embedding only if the pointer moves.
	                // We don't want to do unnecessary action with the element
	                // if an user only clicks/dblclicks on it.
	                this.prepareEmbedding(data);
	                embedding = true;
	            }
	            this.processEmbedding(data, evt, x, y);
	        }

	        this.eventData(evt, {
	            embedding: embedding
	        });
	    },

	    dragMagnet: function(evt, x, y) {
	        this.dragLink(evt, x, y);
	    },

	    // Drag End Handlers

	    dragEnd: function(evt, x, y) {

	        var data = this.eventData(evt);
	        if (data.embedding) { this.finalizeEmbedding(data); }
	    },

	    dragMagnetEnd: function(evt, x, y) {
	        this.dragLinkEnd(evt, x, y);
	    },

	    magnetpointerclick: function(evt, magnet, x, y) {
	        var paper = this.paper;
	        if (paper.eventData(evt).mousemoved > paper.options.clickThreshold) { return; }
	        this.notify('element:magnet:pointerclick', evt, magnet, x, y);
	    }

	}, {

	    Flags: Flags$1,
	});

	assign(ElementView.prototype, elementViewPortPrototype);

	// Does not make any changes to vertices.
	// Returns the arguments that are passed to it, unchanged.
	var normal = function(vertices, opt, linkView) {

	    return vertices;
	};

	// Routes the link always to/from a certain side
	//
	// Arguments:
	//   padding ... gap between the element and the first vertex. :: Default 40.
	//   side ... 'left' | 'right' | 'top' | 'bottom' :: Default 'bottom'.
	//
	var oneSide = function(vertices, opt, linkView) {

	    var side = opt.side || 'bottom';
	    var padding = normalizeSides(opt.padding || 40);

	    // LinkView contains cached source an target bboxes.
	    // Note that those are Geometry rectangle objects.
	    var sourceBBox = linkView.sourceBBox;
	    var targetBBox = linkView.targetBBox;
	    var sourcePoint = sourceBBox.center();
	    var targetPoint = targetBBox.center();

	    var coordinate, dimension, direction;

	    switch (side) {
	        case 'bottom':
	            direction = 1;
	            coordinate = 'y';
	            dimension = 'height';
	            break;
	        case 'top':
	            direction = -1;
	            coordinate = 'y';
	            dimension = 'height';
	            break;
	        case 'left':
	            direction = -1;
	            coordinate = 'x';
	            dimension = 'width';
	            break;
	        case 'right':
	            direction = 1;
	            coordinate = 'x';
	            dimension = 'width';
	            break;
	        default:
	            throw new Error('Router: invalid side');
	    }

	    // move the points from the center of the element to outside of it.
	    sourcePoint[coordinate] += direction * (sourceBBox[dimension] / 2 + padding[side]);
	    targetPoint[coordinate] += direction * (targetBBox[dimension] / 2 + padding[side]);

	    // make link orthogonal (at least the first and last vertex).
	    if ((direction * (sourcePoint[coordinate] - targetPoint[coordinate])) > 0) {
	        targetPoint[coordinate] = sourcePoint[coordinate];
	    } else {
	        sourcePoint[coordinate] = targetPoint[coordinate];
	    }

	    return [sourcePoint].concat(vertices, targetPoint);
	};

	// bearing -> opposite bearing
	var opposites = {
	    N: 'S',
	    S: 'N',
	    E: 'W',
	    W: 'E'
	};

	// bearing -> radians
	var radians = {
	    N: -Math.PI / 2 * 3,
	    S: -Math.PI / 2,
	    E: 0,
	    W: Math.PI
	};

	// HELPERS //

	// returns a point `p` where lines p,p1 and p,p2 are perpendicular and p is not contained
	// in the given box
	function freeJoin(p1, p2, bbox) {

	    var p = new Point(p1.x, p2.y);
	    if (bbox.containsPoint(p)) { p = new Point(p2.x, p1.y); }
	    // kept for reference
	    // if (bbox.containsPoint(p)) p = null;

	    return p;
	}

	// returns either width or height of a bbox based on the given bearing
	function getBBoxSize(bbox, bearing) {

	    return bbox[(bearing === 'W' || bearing === 'E') ? 'width' : 'height'];
	}

	// simple bearing method (calculates only orthogonal cardinals)
	function getBearing(from, to) {

	    if (from.x === to.x) { return (from.y > to.y) ? 'N' : 'S'; }
	    if (from.y === to.y) { return (from.x > to.x) ? 'W' : 'E'; }
	    return null;
	}

	// transform point to a rect
	function getPointBox(p) {

	    return new Rect(p.x, p.y, 0, 0);
	}

	function getPaddingBox(opt) {

	    // if both provided, opt.padding wins over opt.elementPadding
	    var sides = normalizeSides(opt.padding || opt.elementPadding || 20);

	    return {
	        x: -sides.left,
	        y: -sides.top,
	        width: sides.left + sides.right,
	        height: sides.top + sides.bottom
	    };
	}

	// return source bbox
	function getSourceBBox(linkView, opt) {

	    return linkView.sourceBBox.clone().moveAndExpand(getPaddingBox(opt));
	}

	// return target bbox
	function getTargetBBox(linkView, opt) {

	    return linkView.targetBBox.clone().moveAndExpand(getPaddingBox(opt));
	}

	// return source anchor
	function getSourceAnchor(linkView, opt) {

	    if (linkView.sourceAnchor) { return linkView.sourceAnchor; }

	    // fallback: center of bbox
	    var sourceBBox = getSourceBBox(linkView, opt);
	    return sourceBBox.center();
	}

	// return target anchor
	function getTargetAnchor(linkView, opt) {

	    if (linkView.targetAnchor) { return linkView.targetAnchor; }

	    // fallback: center of bbox
	    var targetBBox = getTargetBBox(linkView, opt);
	    return targetBBox.center(); // default
	}

	// PARTIAL ROUTERS //

	function vertexVertex(from, to, bearing) {

	    var p1 = new Point(from.x, to.y);
	    var p2 = new Point(to.x, from.y);
	    var d1 = getBearing(from, p1);
	    var d2 = getBearing(from, p2);
	    var opposite = opposites[bearing];

	    var p = (d1 === bearing || (d1 !== opposite && (d2 === opposite || d2 !== bearing))) ? p1 : p2;

	    return { points: [p], direction: getBearing(p, to) };
	}

	function elementVertex(from, to, fromBBox) {

	    var p = freeJoin(from, to, fromBBox);

	    return { points: [p], direction: getBearing(p, to) };
	}

	function vertexElement(from, to, toBBox, bearing) {

	    var route = {};

	    var points = [new Point(from.x, to.y), new Point(to.x, from.y)];
	    var freePoints = points.filter(function(pt) {
	        return !toBBox.containsPoint(pt);
	    });
	    var freeBearingPoints = freePoints.filter(function(pt) {
	        return getBearing(pt, from) !== bearing;
	    });

	    var p;

	    if (freeBearingPoints.length > 0) {
	        // Try to pick a point which bears the same direction as the previous segment.

	        p = freeBearingPoints.filter(function(pt) {
	            return getBearing(from, pt) === bearing;
	        }).pop();
	        p = p || freeBearingPoints[0];

	        route.points = [p];
	        route.direction = getBearing(p, to);

	    } else {
	        // Here we found only points which are either contained in the element or they would create
	        // a link segment going in opposite direction from the previous one.
	        // We take the point inside element and move it outside the element in the direction the
	        // route is going. Now we can join this point with the current end (using freeJoin).

	        p = difference(points, freePoints)[0];

	        var p2 = (new Point(to)).move(p, -getBBoxSize(toBBox, bearing) / 2);
	        var p1 = freeJoin(p2, from, toBBox);

	        route.points = [p1, p2];
	        route.direction = getBearing(p2, to);
	    }

	    return route;
	}

	function elementElement(from, to, fromBBox, toBBox) {

	    var route = elementVertex(to, from, toBBox);
	    var p1 = route.points[0];

	    if (fromBBox.containsPoint(p1)) {

	        route = elementVertex(from, to, fromBBox);
	        var p2 = route.points[0];

	        if (toBBox.containsPoint(p2)) {

	            var fromBorder = (new Point(from)).move(p2, -getBBoxSize(fromBBox, getBearing(from, p2)) / 2);
	            var toBorder = (new Point(to)).move(p1, -getBBoxSize(toBBox, getBearing(to, p1)) / 2);
	            var mid = (new Line(fromBorder, toBorder)).midpoint();

	            var startRoute = elementVertex(from, mid, fromBBox);
	            var endRoute = vertexVertex(mid, to, startRoute.direction);

	            route.points = [startRoute.points[0], endRoute.points[0]];
	            route.direction = endRoute.direction;
	        }
	    }

	    return route;
	}

	// Finds route for situations where one element is inside the other.
	// Typically the route is directed outside the outer element first and
	// then back towards the inner element.
	function insideElement(from, to, fromBBox, toBBox, bearing) {

	    var route = {};
	    var boundary = fromBBox.union(toBBox).inflate(1);

	    // start from the point which is closer to the boundary
	    var reversed = boundary.center().distance(to) > boundary.center().distance(from);
	    var start = reversed ? to : from;
	    var end = reversed ? from : to;

	    var p1, p2, p3;

	    if (bearing) {
	        // Points on circle with radius equals 'W + H` are always outside the rectangle
	        // with width W and height H if the center of that circle is the center of that rectangle.
	        p1 = Point.fromPolar(boundary.width + boundary.height, radians[bearing], start);
	        p1 = boundary.pointNearestToPoint(p1).move(p1, -1);

	    } else {
	        p1 = boundary.pointNearestToPoint(start).move(start, 1);
	    }

	    p2 = freeJoin(p1, end, boundary);

	    if (p1.round().equals(p2.round())) {
	        p2 = Point.fromPolar(boundary.width + boundary.height, toRad(p1.theta(start)) + Math.PI / 2, end);
	        p2 = boundary.pointNearestToPoint(p2).move(end, 1).round();
	        p3 = freeJoin(p1, p2, boundary);
	        route.points = reversed ? [p2, p3, p1] : [p1, p3, p2];

	    } else {
	        route.points = reversed ? [p2, p1] : [p1, p2];
	    }

	    route.direction = reversed ? getBearing(p1, to) : getBearing(p2, to);

	    return route;
	}

	// MAIN ROUTER //

	// Return points through which a connection needs to be drawn in order to obtain an orthogonal link
	// routing from source to target going through `vertices`.
	function orthogonal(vertices, opt, linkView) {

	    var sourceBBox = getSourceBBox(linkView, opt);
	    var targetBBox = getTargetBBox(linkView, opt);

	    var sourceAnchor = getSourceAnchor(linkView, opt);
	    var targetAnchor = getTargetAnchor(linkView, opt);

	    // if anchor lies outside of bbox, the bbox expands to include it
	    sourceBBox = sourceBBox.union(getPointBox(sourceAnchor));
	    targetBBox = targetBBox.union(getPointBox(targetAnchor));

	    vertices = toArray(vertices).map(Point);
	    vertices.unshift(sourceAnchor);
	    vertices.push(targetAnchor);

	    var bearing; // bearing of previous route segment

	    var orthogonalVertices = []; // the array of found orthogonal vertices to be returned
	    for (var i = 0, max = vertices.length - 1; i < max; i++) {

	        var route = null;

	        var from = vertices[i];
	        var to = vertices[i + 1];

	        var isOrthogonal = !!getBearing(from, to);

	        if (i === 0) { // source

	            if (i + 1 === max) { // route source -> target

	                // Expand one of the elements by 1px to detect situations when the two
	                // elements are positioned next to each other with no gap in between.
	                if (sourceBBox.intersect(targetBBox.clone().inflate(1))) {
	                    route = insideElement(from, to, sourceBBox, targetBBox);

	                } else if (!isOrthogonal) {
	                    route = elementElement(from, to, sourceBBox, targetBBox);
	                }

	            } else { // route source -> vertex

	                if (sourceBBox.containsPoint(to)) {
	                    route = insideElement(from, to, sourceBBox, getPointBox(to).moveAndExpand(getPaddingBox(opt)));

	                } else if (!isOrthogonal) {
	                    route = elementVertex(from, to, sourceBBox);
	                }
	            }

	        } else if (i + 1 === max) { // route vertex -> target

	            // prevent overlaps with previous line segment
	            var isOrthogonalLoop = isOrthogonal && getBearing(to, from) === bearing;

	            if (targetBBox.containsPoint(from) || isOrthogonalLoop) {
	                route = insideElement(from, to, getPointBox(from).moveAndExpand(getPaddingBox(opt)), targetBBox, bearing);

	            } else if (!isOrthogonal) {
	                route = vertexElement(from, to, targetBBox, bearing);
	            }

	        } else if (!isOrthogonal) { // route vertex -> vertex
	            route = vertexVertex(from, to, bearing);
	        }

	        // applicable to all routes:

	        // set bearing for next iteration
	        if (route) {
	            Array.prototype.push.apply(orthogonalVertices, route.points);
	            bearing = route.direction;

	        } else {
	            // orthogonal route and not looped
	            bearing = getBearing(from, to);
	        }

	        // push `to` point to identified orthogonal vertices array
	        if (i + 1 < max) {
	            orthogonalVertices.push(to);
	        }
	    }

	    return orthogonalVertices;
	}

	var config$1 = {

	    // size of the step to find a route (the grid of the manhattan pathfinder)
	    step: 10,

	    // the number of route finding loops that cause the router to abort
	    // returns fallback route instead
	    maximumLoops: 2000,

	    // the number of decimal places to round floating point coordinates
	    precision: 1,

	    // maximum change of direction
	    maxAllowedDirectionChange: 90,

	    // should the router use perpendicular linkView option?
	    // does not connect anchor of element but rather a point close-by that is orthogonal
	    // this looks much better
	    perpendicular: true,

	    // should the source and/or target not be considered as obstacles?
	    excludeEnds: [], // 'source', 'target'

	    // should certain types of elements not be considered as obstacles?
	    excludeTypes: ['basic.Text'],

	    // possible starting directions from an element
	    startDirections: ['top', 'right', 'bottom', 'left'],

	    // possible ending directions to an element
	    endDirections: ['top', 'right', 'bottom', 'left'],

	    // specify the directions used above and what they mean
	    directionMap: {
	        top: { x: 0, y: -1 },
	        right: { x: 1, y: 0 },
	        bottom: { x: 0, y: 1 },
	        left: { x: -1, y: 0 }
	    },

	    // cost of an orthogonal step
	    cost: function() {

	        return this.step;
	    },

	    // an array of directions to find next points on the route
	    // different from start/end directions
	    directions: function() {

	        var step = this.step;
	        var cost = this.cost();

	        return [
	            { offsetX: step, offsetY: 0, cost: cost },
	            { offsetX: -step, offsetY: 0, cost: cost },
	            { offsetX: 0, offsetY: step, cost: cost },
	            { offsetX: 0, offsetY: -step, cost: cost }
	        ];
	    },

	    // a penalty received for direction change
	    penalties: function() {

	        return {
	            0: 0,
	            45: this.step / 2,
	            90: this.step / 2
	        };
	    },

	    // padding applied on the element bounding boxes
	    paddingBox: function() {

	        var step = this.step;

	        return {
	            x: -step,
	            y: -step,
	            width: 2 * step,
	            height: 2 * step
	        };
	    },

	    // A function that determines whether a given point is an obstacle or not.
	    // If used, the `padding`, `excludeEnds`and `excludeTypes` options are ignored.
	    // (point: dia.Point) => boolean;
	    isPointObstacle: null,

	    // a router to use when the manhattan router fails
	    // (one of the partial routes returns null)
	    fallbackRouter: function(vertices, opt, linkView) {

	        if (!isFunction(orthogonal)) {
	            throw new Error('Manhattan requires the orthogonal router as default fallback.');
	        }

	        return orthogonal(vertices, assign({}, config$1, opt), linkView);
	    },

	    /* Deprecated */
	    // a simple route used in situations when main routing method fails
	    // (exceed max number of loop iterations, inaccessible)
	    fallbackRoute: function(from, to, opt) {

	        return null; // null result will trigger the fallbackRouter

	        // left for reference:
	        /*// Find an orthogonal route ignoring obstacles.

	        var point = ((opt.previousDirAngle || 0) % 180 === 0)
	                ? new g.Point(from.x, to.y)
	                : new g.Point(to.x, from.y);

	        return [point];*/
	    },

	    // if a function is provided, it's used to route the link while dragging an end
	    // i.e. function(from, to, opt) { return []; }
	    draggingRoute: null
	};

	// HELPER CLASSES //

	// Map of obstacles
	// Helper structure to identify whether a point lies inside an obstacle.
	function ObstacleMap(opt) {

	    this.map = {};
	    this.options = opt;
	    // tells how to divide the paper when creating the elements map
	    this.mapGridSize = 100;
	}

	ObstacleMap.prototype.build = function(graph, link) {

	    var opt = this.options;

	    // source or target element could be excluded from set of obstacles
	    var excludedEnds = toArray(opt.excludeEnds).reduce(function(res, item) {

	        var end = link.get(item);
	        if (end) {
	            var cell = graph.getCell(end.id);
	            if (cell) {
	                res.push(cell);
	            }
	        }

	        return res;
	    }, []);

	    // Exclude any embedded elements from the source and the target element.
	    var excludedAncestors = [];

	    var source = graph.getCell(link.get('source').id);
	    if (source) {
	        excludedAncestors = union(excludedAncestors, source.getAncestors().map(function(cell) {
	            return cell.id;
	        }));
	    }

	    var target = graph.getCell(link.get('target').id);
	    if (target) {
	        excludedAncestors = union(excludedAncestors, target.getAncestors().map(function(cell) {
	            return cell.id;
	        }));
	    }

	    // Builds a map of all elements for quicker obstacle queries (i.e. is a point contained
	    // in any obstacle?) (a simplified grid search).
	    // The paper is divided into smaller cells, where each holds information about which
	    // elements belong to it. When we query whether a point lies inside an obstacle we
	    // don't need to go through all obstacles, we check only those in a particular cell.
	    var mapGridSize = this.mapGridSize;

	    graph.getElements().reduce(function(map, element) {

	        var isExcludedType = toArray(opt.excludeTypes).includes(element.get('type'));
	        var isExcludedEnd = excludedEnds.find(function(excluded) {
	            return excluded.id === element.id;
	        });
	        var isExcludedAncestor = excludedAncestors.includes(element.id);

	        var isExcluded = isExcludedType || isExcludedEnd || isExcludedAncestor;
	        if (!isExcluded) {
	            var bbox = element.getBBox().moveAndExpand(opt.paddingBox);

	            var origin = bbox.origin().snapToGrid(mapGridSize);
	            var corner = bbox.corner().snapToGrid(mapGridSize);

	            for (var x = origin.x; x <= corner.x; x += mapGridSize) {
	                for (var y = origin.y; y <= corner.y; y += mapGridSize) {
	                    var gridKey = x + '@' + y;
	                    map[gridKey] = map[gridKey] || [];
	                    map[gridKey].push(bbox);
	                }
	            }
	        }

	        return map;
	    }, this.map);

	    return this;
	};

	ObstacleMap.prototype.isPointAccessible = function(point) {

	    var mapKey = point.clone().snapToGrid(this.mapGridSize).toString();

	    return toArray(this.map[mapKey]).every(function(obstacle) {
	        return !obstacle.containsPoint(point);
	    });
	};

	// Sorted Set
	// Set of items sorted by given value.
	function SortedSet() {
	    this.items = [];
	    this.hash = {};
	    this.values = {};
	    this.OPEN = 1;
	    this.CLOSE = 2;
	}

	SortedSet.prototype.add = function(item, value) {

	    if (this.hash[item]) {
	        // item removal
	        this.items.splice(this.items.indexOf(item), 1);
	    } else {
	        this.hash[item] = this.OPEN;
	    }

	    this.values[item] = value;

	    var index$1 = sortedIndex(this.items, item, function(i) {
	        return this.values[i];
	    }.bind(this));

	    this.items.splice(index$1, 0, item);
	};

	SortedSet.prototype.remove = function(item) {

	    this.hash[item] = this.CLOSE;
	};

	SortedSet.prototype.isOpen = function(item) {

	    return this.hash[item] === this.OPEN;
	};

	SortedSet.prototype.isClose = function(item) {

	    return this.hash[item] === this.CLOSE;
	};

	SortedSet.prototype.isEmpty = function() {

	    return this.items.length === 0;
	};

	SortedSet.prototype.pop = function() {

	    var item = this.items.shift();
	    this.remove(item);
	    return item;
	};

	// HELPERS //

	// return source bbox
	function getSourceBBox$1(linkView, opt) {

	    // expand by padding box
	    if (opt && opt.paddingBox) { return linkView.sourceBBox.clone().moveAndExpand(opt.paddingBox); }

	    return linkView.sourceBBox.clone();
	}

	// return target bbox
	function getTargetBBox$1(linkView, opt) {

	    // expand by padding box
	    if (opt && opt.paddingBox) { return linkView.targetBBox.clone().moveAndExpand(opt.paddingBox); }

	    return linkView.targetBBox.clone();
	}

	// return source anchor
	function getSourceAnchor$1(linkView, opt) {

	    if (linkView.sourceAnchor) { return linkView.sourceAnchor; }

	    // fallback: center of bbox
	    var sourceBBox = getSourceBBox$1(linkView, opt);
	    return sourceBBox.center();
	}

	// return target anchor
	function getTargetAnchor$1(linkView, opt) {

	    if (linkView.targetAnchor) { return linkView.targetAnchor; }

	    // fallback: center of bbox
	    var targetBBox = getTargetBBox$1(linkView, opt);
	    return targetBBox.center(); // default
	}

	// returns a direction index from start point to end point
	// corrects for grid deformation between start and end
	function getDirectionAngle(start, end, numDirections, grid, opt) {

	    var quadrant = 360 / numDirections;
	    var angleTheta = start.theta(fixAngleEnd(start, end, grid, opt));
	    var normalizedAngle = normalizeAngle(angleTheta + (quadrant / 2));
	    return quadrant * Math.floor(normalizedAngle / quadrant);
	}

	// helper function for getDirectionAngle()
	// corrects for grid deformation
	// (if a point is one grid steps away from another in both dimensions,
	// it is considered to be 45 degrees away, even if the real angle is different)
	// this causes visible angle discrepancies if `opt.step` is much larger than `paper.gridSize`
	function fixAngleEnd(start, end, grid, opt) {

	    var step = opt.step;

	    var diffX = end.x - start.x;
	    var diffY = end.y - start.y;

	    var gridStepsX = diffX / grid.x;
	    var gridStepsY = diffY / grid.y;

	    var distanceX = gridStepsX * step;
	    var distanceY = gridStepsY * step;

	    return new Point(start.x + distanceX, start.y + distanceY);
	}

	// return the change in direction between two direction angles
	function getDirectionChange(angle1, angle2) {

	    var directionChange = Math.abs(angle1 - angle2);
	    return (directionChange > 180) ? (360 - directionChange) : directionChange;
	}

	// fix direction offsets according to current grid
	function getGridOffsets(directions, grid, opt) {

	    var step = opt.step;

	    toArray(opt.directions).forEach(function(direction) {

	        direction.gridOffsetX = (direction.offsetX / step) * grid.x;
	        direction.gridOffsetY = (direction.offsetY / step) * grid.y;
	    });
	}

	// get grid size in x and y dimensions, adapted to source and target positions
	function getGrid(step, source, target) {

	    return {
	        source: source.clone(),
	        x: getGridDimension(target.x - source.x, step),
	        y: getGridDimension(target.y - source.y, step)
	    };
	}

	// helper function for getGrid()
	function getGridDimension(diff, step) {

	    // return step if diff = 0
	    if (!diff) { return step; }

	    var absDiff = Math.abs(diff);
	    var numSteps = Math.round(absDiff / step);

	    // return absDiff if less than one step apart
	    if (!numSteps) { return absDiff; }

	    // otherwise, return corrected step
	    var roundedDiff = numSteps * step;
	    var remainder = absDiff - roundedDiff;
	    var stepCorrection = remainder / numSteps;

	    return step + stepCorrection;
	}

	// return a clone of point snapped to grid
	function snapToGrid$1(point, grid) {

	    var source = grid.source;

	    var snappedX = snapToGrid(point.x - source.x, grid.x) + source.x;
	    var snappedY = snapToGrid(point.y - source.y, grid.y) + source.y;

	    return new Point(snappedX, snappedY);
	}

	// round the point to opt.precision
	function round$4(point, precision) {

	    return point.round(precision);
	}

	// snap to grid and then round the point
	function align(point, grid, precision) {

	    return round$4(snapToGrid$1(point.clone(), grid), precision);
	}

	// return a string representing the point
	// string is rounded in both dimensions
	function getKey(point) {

	    return point.clone().toString();
	}

	// return a normalized vector from given point
	// used to determine the direction of a difference of two points
	function normalizePoint(point) {

	    return new Point(
	        point.x === 0 ? 0 : Math.abs(point.x) / point.x,
	        point.y === 0 ? 0 : Math.abs(point.y) / point.y
	    );
	}

	// PATHFINDING //

	// reconstructs a route by concatenating points with their parents
	function reconstructRoute(parents, points, tailPoint, from, to, grid, opt) {

	    var route = [];

	    var prevDiff = normalizePoint(to.difference(tailPoint));

	    // tailPoint is assumed to be aligned already
	    var currentKey = getKey(tailPoint);
	    var parent = parents[currentKey];

	    var point;
	    while (parent) {

	        // point is assumed to be aligned already
	        point = points[currentKey];

	        var diff = normalizePoint(point.difference(parent));
	        if (!diff.equals(prevDiff)) {
	            route.unshift(point);
	            prevDiff = diff;
	        }

	        // parent is assumed to be aligned already
	        currentKey = getKey(parent);
	        parent = parents[currentKey];
	    }

	    // leadPoint is assumed to be aligned already
	    var leadPoint = points[currentKey];

	    var fromDiff = normalizePoint(leadPoint.difference(from));
	    if (!fromDiff.equals(prevDiff)) {
	        route.unshift(leadPoint);
	    }

	    return route;
	}

	// heuristic method to determine the distance between two points
	function estimateCost(from, endPoints) {

	    var min = Infinity;

	    for (var i = 0, len = endPoints.length; i < len; i++) {
	        var cost = from.manhattanDistance(endPoints[i]);
	        if (cost < min) { min = cost; }
	    }

	    return min;
	}

	// find points around the bbox taking given directions into account
	// lines are drawn from anchor in given directions, intersections recorded
	// if anchor is outside bbox, only those directions that intersect get a rect point
	// the anchor itself is returned as rect point (representing some directions)
	// (since those directions are unobstructed by the bbox)
	function getRectPoints(anchor, bbox, directionList, grid, opt) {

	    var precision = opt.precision;
	    var directionMap = opt.directionMap;

	    var anchorCenterVector = anchor.difference(bbox.center());

	    var keys = isObject$1(directionMap) ? Object.keys(directionMap) : [];
	    var dirList = toArray(directionList);
	    var rectPoints = keys.reduce(function(res, key) {

	        if (dirList.includes(key)) {
	            var direction = directionMap[key];

	            // create a line that is guaranteed to intersect the bbox if bbox is in the direction
	            // even if anchor lies outside of bbox
	            var endpoint = new Point(
	                anchor.x + direction.x * (Math.abs(anchorCenterVector.x) + bbox.width),
	                anchor.y + direction.y * (Math.abs(anchorCenterVector.y) + bbox.height)
	            );
	            var intersectionLine = new Line(anchor, endpoint);

	            // get the farther intersection, in case there are two
	            // (that happens if anchor lies next to bbox)
	            var intersections = intersectionLine.intersect(bbox) || [];
	            var numIntersections = intersections.length;
	            var farthestIntersectionDistance;
	            var farthestIntersection = null;
	            for (var i = 0; i < numIntersections; i++) {
	                var currentIntersection = intersections[i];
	                var distance = anchor.squaredDistance(currentIntersection);
	                if ((farthestIntersectionDistance === undefined) || (distance > farthestIntersectionDistance)) {
	                    farthestIntersectionDistance = distance;
	                    farthestIntersection = currentIntersection;
	                }
	            }

	            // if an intersection was found in this direction, it is our rectPoint
	            if (farthestIntersection) {
	                var point = align(farthestIntersection, grid, precision);

	                // if the rectPoint lies inside the bbox, offset it by one more step
	                if (bbox.containsPoint(point)) {
	                    point = align(point.offset(direction.x * grid.x, direction.y * grid.y), grid, precision);
	                }

	                // then add the point to the result array
	                // aligned
	                res.push(point);
	            }
	        }

	        return res;
	    }, []);

	    // if anchor lies outside of bbox, add it to the array of points
	    if (!bbox.containsPoint(anchor)) {
	        // aligned
	        rectPoints.push(align(anchor, grid, precision));
	    }

	    return rectPoints;
	}

	// finds the route between two points/rectangles (`from`, `to`) implementing A* algorithm
	// rectangles get rect points assigned by getRectPoints()
	function findRoute(from, to, isPointObstacle, opt) {

	    var precision = opt.precision;

	    // Get grid for this route.

	    var sourceAnchor, targetAnchor;

	    if (from instanceof Rect) { // `from` is sourceBBox
	        sourceAnchor = round$4(getSourceAnchor$1(this, opt).clone(), precision);
	    } else {
	        sourceAnchor = round$4(from.clone(), precision);
	    }

	    if (to instanceof Rect) { // `to` is targetBBox
	        targetAnchor = round$4(getTargetAnchor$1(this, opt).clone(), precision);
	    } else {
	        targetAnchor = round$4(to.clone(), precision);
	    }

	    var grid = getGrid(opt.step, sourceAnchor, targetAnchor);

	    // Get pathfinding points.

	    var start, end; // aligned with grid by definition
	    var startPoints, endPoints; // assumed to be aligned with grid already

	    // set of points we start pathfinding from
	    if (from instanceof Rect) { // `from` is sourceBBox
	        start = sourceAnchor;
	        startPoints = getRectPoints(start, from, opt.startDirections, grid, opt);

	    } else {
	        start = sourceAnchor;
	        startPoints = [start];
	    }

	    // set of points we want the pathfinding to finish at
	    if (to instanceof Rect) { // `to` is targetBBox
	        end = targetAnchor;
	        endPoints = getRectPoints(targetAnchor, to, opt.endDirections, grid, opt);

	    } else {
	        end = targetAnchor;
	        endPoints = [end];
	    }

	    // take into account only accessible rect points (those not under obstacles)
	    startPoints = startPoints.filter(function (p) { return !isPointObstacle(p); });
	    endPoints = endPoints.filter(function (p) { return !isPointObstacle(p); });

	    // Check that there is an accessible route point on both sides.
	    // Otherwise, use fallbackRoute().
	    if (startPoints.length > 0 && endPoints.length > 0) {

	        // The set of tentative points to be evaluated, initially containing the start points.
	        // Rounded to nearest integer for simplicity.
	        var openSet = new SortedSet();
	        // Keeps reference to actual points for given elements of the open set.
	        var points = {};
	        // Keeps reference to a point that is immediate predecessor of given element.
	        var parents = {};
	        // Cost from start to a point along best known path.
	        var costs = {};

	        for (var i = 0, n = startPoints.length; i < n; i++) {
	            // startPoint is assumed to be aligned already
	            var startPoint = startPoints[i];

	            var key = getKey(startPoint);

	            openSet.add(key, estimateCost(startPoint, endPoints));
	            points[key] = startPoint;
	            costs[key] = 0;
	        }

	        var previousRouteDirectionAngle = opt.previousDirectionAngle; // undefined for first route
	        var isPathBeginning = (previousRouteDirectionAngle === undefined);

	        // directions
	        var direction, directionChange;
	        var directions = opt.directions;
	        getGridOffsets(directions, grid, opt);

	        var numDirections = directions.length;

	        var endPointsKeys = toArray(endPoints).reduce(function(res, endPoint) {
	            // endPoint is assumed to be aligned already

	            var key = getKey(endPoint);
	            res.push(key);
	            return res;
	        }, []);

	        // main route finding loop
	        var loopsRemaining = opt.maximumLoops;
	        while (!openSet.isEmpty() && loopsRemaining > 0) {

	            // remove current from the open list
	            var currentKey = openSet.pop();
	            var currentPoint = points[currentKey];
	            var currentParent = parents[currentKey];
	            var currentCost = costs[currentKey];

	            var isRouteBeginning = (currentParent === undefined); // undefined for route starts
	            var isStart = currentPoint.equals(start); // (is source anchor or `from` point) = can leave in any direction

	            var previousDirectionAngle;
	            if (!isRouteBeginning) { previousDirectionAngle = getDirectionAngle(currentParent, currentPoint, numDirections, grid, opt); } // a vertex on the route
	            else if (!isPathBeginning) { previousDirectionAngle = previousRouteDirectionAngle; } // beginning of route on the path
	            else if (!isStart) { previousDirectionAngle = getDirectionAngle(start, currentPoint, numDirections, grid, opt); } // beginning of path, start rect point
	            else { previousDirectionAngle = null; } // beginning of path, source anchor or `from` point

	            // check if we reached any endpoint
	            var samePoints = startPoints.length === endPoints.length;
	            if (samePoints) {
	                for (var j = 0; j < startPoints.length; j++) {
	                    if (!startPoints[j].equals(endPoints[j])) {
	                        samePoints = false;
	                        break;
	                    }
	                }
	            }
	            var skipEndCheck = (isRouteBeginning && samePoints);
	            if (!skipEndCheck && (endPointsKeys.indexOf(currentKey) >= 0)) {
	                opt.previousDirectionAngle = previousDirectionAngle;
	                return reconstructRoute(parents, points, currentPoint, start, end, grid, opt);
	            }

	            // go over all possible directions and find neighbors
	            for (i = 0; i < numDirections; i++) {
	                direction = directions[i];

	                var directionAngle = direction.angle;
	                directionChange = getDirectionChange(previousDirectionAngle, directionAngle);

	                // if the direction changed rapidly, don't use this point
	                // any direction is allowed for starting points
	                if (!(isPathBeginning && isStart) && directionChange > opt.maxAllowedDirectionChange) { continue; }

	                var neighborPoint = align(currentPoint.clone().offset(direction.gridOffsetX, direction.gridOffsetY), grid, precision);
	                var neighborKey = getKey(neighborPoint);

	                // Closed points from the openSet were already evaluated.
	                if (openSet.isClose(neighborKey) || isPointObstacle(neighborPoint)) { continue; }

	                // We can only enter end points at an acceptable angle.
	                if (endPointsKeys.indexOf(neighborKey) >= 0) { // neighbor is an end point

	                    var isNeighborEnd = neighborPoint.equals(end); // (is target anchor or `to` point) = can be entered in any direction

	                    if (!isNeighborEnd) {
	                        var endDirectionAngle = getDirectionAngle(neighborPoint, end, numDirections, grid, opt);
	                        var endDirectionChange = getDirectionChange(directionAngle, endDirectionAngle);

	                        if (endDirectionChange > opt.maxAllowedDirectionChange) { continue; }
	                    }
	                }

	                // The current direction is ok.

	                var neighborCost = direction.cost;
	                var neighborPenalty = isStart ? 0 : opt.penalties[directionChange]; // no penalties for start point
	                var costFromStart = currentCost + neighborCost + neighborPenalty;

	                if (!openSet.isOpen(neighborKey) || (costFromStart < costs[neighborKey])) {
	                    // neighbor point has not been processed yet
	                    // or the cost of the path from start is lower than previously calculated

	                    points[neighborKey] = neighborPoint;
	                    parents[neighborKey] = currentPoint;
	                    costs[neighborKey] = costFromStart;
	                    openSet.add(neighborKey, costFromStart + estimateCost(neighborPoint, endPoints));
	                }
	            }

	            loopsRemaining--;
	        }
	    }

	    // no route found (`to` point either wasn't accessible or finding route took
	    // way too much calculation)
	    return opt.fallbackRoute.call(this, start, end, opt);
	}

	// resolve some of the options
	function resolveOptions(opt) {

	    opt.directions = result(opt, 'directions');
	    opt.penalties = result(opt, 'penalties');
	    opt.paddingBox = result(opt, 'paddingBox');
	    opt.padding = result(opt, 'padding');

	    if (opt.padding) {
	        // if both provided, opt.padding wins over opt.paddingBox
	        var sides = normalizeSides(opt.padding);
	        opt.paddingBox = {
	            x: -sides.left,
	            y: -sides.top,
	            width: sides.left + sides.right,
	            height: sides.top + sides.bottom
	        };
	    }

	    toArray(opt.directions).forEach(function(direction) {

	        var point1 = new Point(0, 0);
	        var point2 = new Point(direction.offsetX, direction.offsetY);

	        direction.angle = normalizeAngle(point1.theta(point2));
	    });
	}

	// initialization of the route finding
	function router(vertices, opt, linkView) {

	    resolveOptions(opt);

	    // enable/disable linkView perpendicular option
	    linkView.options.perpendicular = !!opt.perpendicular;

	    var sourceBBox = getSourceBBox$1(linkView, opt);
	    var targetBBox = getTargetBBox$1(linkView, opt);

	    var sourceAnchor = getSourceAnchor$1(linkView, opt);
	    //var targetAnchor = getTargetAnchor(linkView, opt);

	    // pathfinding
	    var isPointObstacle;
	    if (typeof opt.isPointObstacle === 'function') {
	        isPointObstacle = opt.isPointObstacle;
	    } else {
	        var map = new ObstacleMap(opt);
	        map.build(linkView.paper.model, linkView.model);
	        isPointObstacle = function (point) { return !map.isPointAccessible(point); };
	    }

	    var oldVertices = toArray(vertices).map(Point);
	    var newVertices = [];
	    var tailPoint = sourceAnchor; // the origin of first route's grid, does not need snapping

	    // find a route by concatenating all partial routes (routes need to pass through vertices)
	    // source -> vertex[1] -> ... -> vertex[n] -> target
	    var to, from;

	    for (var i = 0, len = oldVertices.length; i <= len; i++) {

	        var partialRoute = null;

	        from = to || sourceBBox;
	        to = oldVertices[i];

	        if (!to) {
	            // this is the last iteration
	            // we ran through all vertices in oldVertices
	            // 'to' is not a vertex.

	            to = targetBBox;

	            // If the target is a point (i.e. it's not an element), we
	            // should use dragging route instead of main routing method if it has been provided.
	            var isEndingAtPoint = !linkView.model.get('source').id || !linkView.model.get('target').id;

	            if (isEndingAtPoint && isFunction(opt.draggingRoute)) {
	                // Make sure we are passing points only (not rects).
	                var dragFrom = (from === sourceBBox) ? sourceAnchor : from;
	                var dragTo = to.origin();

	                partialRoute = opt.draggingRoute.call(linkView, dragFrom, dragTo, opt);
	            }
	        }

	        // if partial route has not been calculated yet use the main routing method to find one
	        partialRoute = partialRoute || findRoute.call(linkView, from, to, isPointObstacle, opt);

	        if (partialRoute === null) { // the partial route cannot be found
	            return opt.fallbackRouter(vertices, opt, linkView);
	        }

	        var leadPoint = partialRoute[0];

	        // remove the first point if the previous partial route had the same point as last
	        if (leadPoint && leadPoint.equals(tailPoint)) { partialRoute.shift(); }

	        // save tailPoint for next iteration
	        tailPoint = partialRoute[partialRoute.length - 1] || tailPoint;

	        Array.prototype.push.apply(newVertices, partialRoute);
	    }

	    return newVertices;
	}

	// public function
	var manhattan = function(vertices, opt, linkView) {
	    return router(vertices, assign({}, config$1, opt), linkView);
	};

	var config$2 = {

	    maxAllowedDirectionChange: 45,

	    // cost of a diagonal step
	    diagonalCost: function() {

	        var step = this.step;
	        return Math.ceil(Math.sqrt(step * step << 1));
	    },

	    // an array of directions to find next points on the route
	    // different from start/end directions
	    directions: function() {

	        var step = this.step;
	        var cost = this.cost();
	        var diagonalCost = this.diagonalCost();

	        return [
	            { offsetX: step, offsetY: 0, cost: cost },
	            { offsetX: step, offsetY: step, cost: diagonalCost },
	            { offsetX: 0, offsetY: step, cost: cost },
	            { offsetX: -step, offsetY: step, cost: diagonalCost },
	            { offsetX: -step, offsetY: 0, cost: cost },
	            { offsetX: -step, offsetY: -step, cost: diagonalCost },
	            { offsetX: 0, offsetY: -step, cost: cost },
	            { offsetX: step, offsetY: -step, cost: diagonalCost }
	        ];
	    },

	    // a simple route used in situations when main routing method fails
	    // (exceed max number of loop iterations, inaccessible)
	    fallbackRoute: function(from, to, opt) {

	        // Find a route which breaks by 45 degrees ignoring all obstacles.

	        var theta = from.theta(to);

	        var route = [];

	        var a = { x: to.x, y: from.y };
	        var b = { x: from.x, y: to.y };

	        if (theta % 180 > 90) {
	            var t = a;
	            a = b;
	            b = t;
	        }

	        var p1 = (theta % 90) < 45 ? a : b;
	        var l1 = new Line(from, p1);

	        var alpha = 90 * Math.ceil(theta / 90);

	        var p2 = Point.fromPolar(l1.squaredLength(), toRad(alpha + 135), p1);
	        var l2 = new Line(to, p2);

	        var intersectionPoint = l1.intersection(l2);
	        var point = intersectionPoint ? intersectionPoint : to;

	        var directionFrom = intersectionPoint ? point : from;

	        var quadrant = 360 / opt.directions.length;
	        var angleTheta = directionFrom.theta(to);
	        var normalizedAngle = normalizeAngle(angleTheta + (quadrant / 2));
	        var directionAngle = quadrant * Math.floor(normalizedAngle / quadrant);

	        opt.previousDirectionAngle = directionAngle;

	        if (point) { route.push(point.round()); }
	        route.push(to);

	        return route;
	    }
	};

	// public function
	var metro = function(vertices, opt, linkView) {

	    if (!isFunction(manhattan)) {
	        throw new Error('Metro requires the manhattan router.');
	    }

	    return manhattan(vertices, assign({}, config$2, opt), linkView);
	};

	var Directions = {
	    AUTO: 'auto',
	    LEFT: 'left',
	    RIGHT: 'right',
	    TOP: 'top',
	    BOTTOM: 'bottom',
	    ANCHOR_SIDE: 'anchor-side',
	    MAGNET_SIDE: 'magnet-side'
	};

	var DEFINED_DIRECTIONS = [Directions.LEFT, Directions.RIGHT, Directions.TOP, Directions.BOTTOM];

	function getDirectionForLinkConnection(linkOrigin, connectionPoint, linkView) {
	    var tangent = linkView.getTangentAtLength(linkView.getClosestPointLength(connectionPoint));
	    var roundedAngle = Math.round(tangent.angle() / 90) * 90;

	    switch (roundedAngle) {
	        case 0:
	        case 360:
	            return linkOrigin.y < connectionPoint.y ? Directions.TOP : Directions.BOTTOM;
	        case 90:
	            return linkOrigin.x < connectionPoint.x ? Directions.LEFT : Directions.RIGHT;
	        case 180:
	            return linkOrigin.y < connectionPoint.y ? Directions.TOP : Directions.BOTTOM;
	        case 270:
	            return linkOrigin.x < connectionPoint.x ? Directions.LEFT : Directions.RIGHT;
	    }
	}

	function rightAngleRouter(_vertices, opt, linkView) {
	    var margin = opt.margin || 20;
	    var sourceDirection = opt.sourceDirection; if ( sourceDirection === void 0 ) sourceDirection = Directions.AUTO;
	    var targetDirection = opt.targetDirection; if ( targetDirection === void 0 ) targetDirection = Directions.AUTO;

	    var sourceView = linkView.sourceView;
	    var targetView = linkView.targetView;

	    var isSourcePort = !!linkView.model.source().port;
	    var isTargetPort = !!linkView.model.target().port;

	    if (sourceDirection === Directions.AUTO) {
	        sourceDirection = isSourcePort ? Directions.MAGNET_SIDE : Directions.ANCHOR_SIDE;
	    }

	    if (targetDirection === Directions.AUTO) {
	        targetDirection = isTargetPort ? Directions.MAGNET_SIDE : Directions.ANCHOR_SIDE;
	    }

	    var sourceBBox = linkView.sourceBBox;
	    var targetBBox = linkView.targetBBox;
	    var sourcePoint = linkView.sourceAnchor;
	    var targetPoint = linkView.targetAnchor;
	    var ref = sourceView && sourceView.model.isElement() ? Rect.fromRectUnion(sourceBBox, sourceView.model.getBBox()) : linkView.sourceAnchor;
	    var sx0 = ref.x;
	    var sy0 = ref.y;
	    var sourceWidth = ref.width; if ( sourceWidth === void 0 ) sourceWidth = 0;
	    var sourceHeight = ref.height; if ( sourceHeight === void 0 ) sourceHeight = 0;

	    var ref$1 = targetView && targetView.model.isElement() ? Rect.fromRectUnion(targetBBox, targetView.model.getBBox()) : linkView.targetAnchor;
	    var tx0 = ref$1.x;
	    var ty0 = ref$1.y;
	    var targetWidth = ref$1.width; if ( targetWidth === void 0 ) targetWidth = 0;
	    var targetHeight = ref$1.height; if ( targetHeight === void 0 ) targetHeight = 0;

	    var tx1 = tx0 + targetWidth;
	    var ty1 = ty0 + targetHeight;
	    var sx1 = sx0 + sourceWidth;
	    var sy1 = sy0 + sourceHeight;

	    // Key coordinates including the margin
	    var smx0 = sx0 - margin;
	    var smx1 = sx1 + margin;
	    var smy0 = sy0 - margin;
	    var smy1 = sy1 + margin;
	    var tmx0 = tx0 - margin;
	    var tmx1 = tx1 + margin;
	    var tmy0 = ty0 - margin;
	    var tmy1 = ty1 + margin;

	    var sourceOutsidePoint = sourcePoint.clone();

	    var sourceSide;

	    if (!sourceView) {
	        var sourceLinkAnchorBBox = new Rect(sx0, sy0, 0, 0);
	        sourceSide = DEFINED_DIRECTIONS.includes(sourceDirection) ? sourceDirection : sourceLinkAnchorBBox.sideNearestToPoint(targetPoint);
	    } else if (sourceView.model.isLink()) {
	        sourceSide = getDirectionForLinkConnection(targetPoint, sourcePoint, sourceView);
	    } else if (sourceDirection === Directions.ANCHOR_SIDE) {
	        sourceSide = sourceBBox.sideNearestToPoint(sourcePoint);
	    } else if (sourceDirection === Directions.MAGNET_SIDE) {
	        sourceSide = sourceView.model.getBBox().sideNearestToPoint(sourcePoint);
	    } else {
	        sourceSide = sourceDirection;
	    }

	    switch (sourceSide) {
	        case 'left':
	            sourceOutsidePoint.x = smx0;
	            break;
	        case 'right':
	            sourceOutsidePoint.x = smx1;
	            break;
	        case 'top':
	            sourceOutsidePoint.y = smy0;
	            break;
	        case 'bottom':
	            sourceOutsidePoint.y = smy1;
	            break;
	    }
	    var targetOutsidePoint = targetPoint.clone();


	    var targetSide;


	    if (!targetView) {
	        var targetLinkAnchorBBox = new Rect(tx0, ty0, 0, 0);
	        targetSide = DEFINED_DIRECTIONS.includes(targetDirection) ? targetDirection : targetLinkAnchorBBox.sideNearestToPoint(sourcePoint);
	    } else if (targetView.model.isLink()) {
	        targetSide = getDirectionForLinkConnection(sourcePoint, targetPoint, targetView);
	    } else if (targetDirection === Directions.ANCHOR_SIDE) {
	        targetSide = targetBBox.sideNearestToPoint(targetPoint);
	    } else if (targetDirection === Directions.MAGNET_SIDE) {
	        targetSide = targetView.model.getBBox().sideNearestToPoint(targetPoint);
	    } else {
	        targetSide = targetDirection;
	    }

	    switch (targetSide) {
	        case 'left':
	            targetOutsidePoint.x = tmx0;
	            break;
	        case 'right':
	            targetOutsidePoint.x = tmx1;
	            break;
	        case 'top':
	            targetOutsidePoint.y = tmy0;
	            break;
	        case 'bottom':
	            targetOutsidePoint.y = tmy1;
	            break;
	    }

	    var sox = sourceOutsidePoint.x;
	    var soy = sourceOutsidePoint.y;
	    var tox = targetOutsidePoint.x;
	    var toy = targetOutsidePoint.y;
	    var tcx = (tx0 + tx1) / 2;
	    var tcy = (ty0 + ty1) / 2;
	    var scx = (sx0 + sx1) / 2;
	    var scy = (sy0 + sy1) / 2;
	    var middleOfVerticalSides = (scx < tcx ? (sx1 + tx0) : (tx1 + sx0)) / 2;
	    var middleOfHorizontalSides = (scy < tcy ? (sy1 + ty0) : (ty1 + sy0)) / 2;

	    if (sourceSide === 'left' && targetSide === 'right') {
	        if (smx0 <= tx1) {
	            var y = middleOfHorizontalSides;
	            if (sx1 <= tx0) {
	                if (ty1 >= smy0 && toy < soy) {
	                    y = Math.min(tmy0, smy0);
	                } else if (ty0 <= smy1 && toy >= soy) {
	                    y = Math.max(tmy1, smy1);
	                }
	            }
	            return [
	                { x: sox, y: soy },
	                { x: sox, y: y },
	                { x: tox, y: y },
	                { x: tox, y: toy }
	            ];
	        }

	        var x = (sox + tox) / 2;
	        return [
	            { x: x, y: soy },
	            { x: x, y: toy }
	        ];
	    } else if (sourceSide === 'right' && targetSide === 'left') {
	        if (smx1 >= tx0) {
	            var y$1 = middleOfHorizontalSides;
	            if (sox > tx1) {
	                if (ty1 >= smy0 && toy < soy) {
	                    y$1 = Math.min(tmy0, smy0);
	                } else if (ty0 <= smy1 && toy >= soy) {
	                    y$1 = Math.max(tmy1, smy1);
	                }
	            }

	            return [
	                { x: sox, y: soy },
	                { x: sox, y: y$1 },
	                { x: tox, y: y$1 },
	                { x: tox, y: toy }
	            ];
	        }

	        var x$1 = (sox + tox) / 2;
	        return [
	            { x: x$1, y: soy },
	            { x: x$1, y: toy }
	        ];
	    } else if (sourceSide === 'top' && targetSide === 'bottom') {
	        if (soy < toy) {
	            var x$2 = middleOfVerticalSides;
	            var y$2 = soy;

	            if (soy < ty0) {
	                if (tx1 >= smx0 && tox < sox) {
	                    x$2 = Math.min(tmx0, smx0);
	                } else if (tx0 <= smx1 && tox >= sox) {
	                    x$2 = Math.max(tmx1, smx1);
	                }
	            }

	            return [
	                { x: sox, y: y$2 },
	                { x: x$2, y: y$2 },
	                { x: x$2, y: toy },
	                { x: tox, y: toy }
	            ];
	        }
	        var y$3 = (soy + toy) / 2;
	        return [
	            { x: sox, y: y$3 },
	            { x: tox, y: y$3 }
	        ];
	    } else if (sourceSide === 'bottom' && targetSide === 'top') {
	        if (soy - margin > toy) {
	            var x$3 = middleOfVerticalSides;
	            var y$4 = soy;

	            if (soy > ty1) {
	                if (tx1 >= smx0 && tox < sox) {
	                    x$3 = Math.min(tmx0, smx0);
	                } else if (tx0 <= smx1 && tox >= sox) {
	                    x$3 = Math.max(tmx1, smx1);
	                }
	            }

	            return [
	                { x: sox, y: y$4 },
	                { x: x$3, y: y$4 },
	                { x: x$3, y: toy },
	                { x: tox, y: toy }
	            ];
	        }
	        var y$5 = (soy + toy) / 2;
	        return [
	            { x: sox, y: y$5 },
	            { x: tox, y: y$5 }
	        ];
	    } else if (sourceSide === 'top' && targetSide === 'top') {
	        var x$4;
	        var y1 = Math.min((sy1 + ty0) / 2, toy);
	        var y2 = Math.min((sy0 + ty1) / 2, soy);

	        if (toy < soy) {
	            if (sox >= tmx1 || sox <= tmx0) {
	                return [
	                    { x: sox, y: Math.min(soy,toy) },
	                    { x: tox, y: Math.min(soy,toy) }
	                ];
	            } else if (tox > sox) {
	                x$4 = Math.min(sox, tmx0);
	            } else {
	                x$4 = Math.max(sox, tmx1);
	            }
	        } else {
	            if (tox >= smx1 || tox <= smx0) {
	                return [
	                    { x: sox, y: Math.min(soy,toy) },
	                    { x: tox, y: Math.min(soy,toy) }
	                ];
	            } else if (tox >= sox) {
	                x$4 = Math.max(tox, smx1);
	            } else {
	                x$4 = Math.min(tox, smx0);
	            }
	        }

	        return [
	            { x: sox, y: y2 },
	            { x: x$4, y: y2 },
	            { x: x$4, y: y1 },
	            { x: tox, y: y1 }
	        ];
	    } else if (sourceSide === 'bottom' && targetSide === 'bottom') {
	        if (tx0 >= sox + margin || tx1 <= sox - margin) {
	            return [
	                { x: sox, y: Math.max(soy, toy) },
	                { x: tox, y: Math.max(soy, toy) }
	            ];
	        }

	        var x$5;
	        var y1$1;
	        var y2$1;

	        if (toy > soy) {
	            y1$1 = Math.max((sy1 + ty0) / 2, toy);
	            y2$1 = Math.max((sy1 + ty0) / 2, soy);

	            if (tox > sox) {
	                x$5 = Math.min(sox, tmx0);
	            } else {
	                x$5 = Math.max(sox, tmx1);
	            }
	        } else {
	            y1$1 = Math.max((sy0 + ty1) / 2, toy);
	            y2$1 = Math.max((sy0 + ty1) / 2, soy);

	            if (tox > sox) {
	                x$5 = Math.min(tox, smx0);
	            } else {
	                x$5 = Math.max(tox, smx1);
	            }
	        }

	        return [
	            { x: sox, y: y2$1 },
	            { x: x$5, y: y2$1 },
	            { x: x$5, y: y1$1 },
	            { x: tox, y: y1$1 }
	        ];
	    } else if (sourceSide === 'left' && targetSide === 'left') {
	        var y$6;
	        var x1 = Math.min((sx1 + tx0) / 2, tox);
	        var x2 = Math.min((sx0 + tx1) / 2, sox);

	        if (tox > sox) {
	            if (toy <= soy) {
	                y$6 = Math.min(smy0, toy);
	            } else {
	                y$6 = Math.max(smy1, toy);
	            }
	        } else {
	            if (toy >= soy) {
	                y$6 = Math.min(tmy0, soy);
	            } else {
	                y$6 = Math.max(tmy1, soy);
	            }
	        }

	        return [
	            { x: x2, y: soy },
	            { x: x2, y: y$6 },
	            { x: x1, y: y$6 },
	            { x: x1, y: toy }
	        ];
	    } else if (sourceSide === 'right' && targetSide === 'right') {
	        var y$7;
	        var x1$1 = Math.max((sx0 + tx1) / 2, tox);
	        var x2$1 = Math.max((sx1 + tx0) / 2, sox);

	        if (tox < sox) {
	            if (toy <= soy) {
	                y$7 = Math.min(smy0, toy);
	            } else {
	                y$7 = Math.max(smy1, toy);
	            }
	        } else {
	            if (toy >= soy) {
	                y$7 = Math.min(tmy0, soy);
	            } else {
	                y$7 = Math.max(tmy1, soy);
	            }
	        }

	        return [
	            { x: x2$1, y: soy },
	            { x: x2$1, y: y$7 },
	            { x: x1$1, y: y$7 },
	            { x: x1$1, y: toy }
	        ];
	    } else if (sourceSide === 'top' && targetSide === 'right') {
	        if (soy > toy) {
	            if (sox < tox) {
	                var y$8 = (sy0 + ty1) / 2;
	                if (y$8 > tcy && y$8 < tmy1 && sox < tmx0) {
	                    y$8 = tmy0;
	                }
	                return [
	                    { x: sox, y: y$8 },
	                    { x: tox, y: y$8 },
	                    { x: tox, y: toy }
	                ];
	            }
	            return [{ x: sox, y: toy }];
	        }

	        var x$6 = (sx0 + tx1) / 2;

	        if (sox > tox && sy1 >= toy) {
	            return [
	                { x: sox, y: soy },
	                { x: x$6, y: soy },
	                { x: x$6, y: toy }];
	        }

	        if (x$6 > smx0 && soy < ty1) {
	            var y$9 = Math.min(sy0, ty0) - margin;
	            var x$7 = Math.max(sx1, tx1) + margin;
	            return [
	                { x: sox, y: y$9 },
	                { x: x$7, y: y$9 },
	                { x: x$7, y: toy }
	            ];
	        }
	        return [
	            { x: sox, y: soy },
	            { x: x$6, y: soy },
	            { x: x$6, y: toy }
	        ];
	    } else if (sourceSide === 'top' && targetSide === 'left') {
	        if (soy > toy) {
	            if (sox > tox) {
	                var y$10 = (sy0 + ty1) / 2;
	                if (y$10 > tcy && y$10 < tmy1 && sox > tmx1) {
	                    y$10 = tmy0;
	                }
	                return [
	                    { x: sox, y: y$10 },
	                    { x: tox, y: y$10 },
	                    { x: tox, y: toy }
	                ];
	            }
	            return [{ x: sox, y: toy }];
	        }

	        var x$8 = (sx1 + tx0) / 2;

	        if (sox < tox && sy1 >= toy) {
	            return [
	                { x: sox, y: soy },
	                { x: x$8, y: soy },
	                { x: x$8, y: toy }];
	        }

	        if (x$8 < smx1 && soy < ty1) {
	            var y$11 = Math.min(sy0, ty0) - margin;
	            var x$9 = Math.min(sx0, tx0) - margin;
	            return [
	                { x: sox, y: y$11 },
	                { x: x$9, y: y$11 },
	                { x: x$9, y: toy }
	            ];
	        }
	        return [
	            { x: sox, y: soy },
	            { x: x$8, y: soy },
	            { x: x$8, y: toy }
	        ];
	    } else if (sourceSide === 'bottom' && targetSide === 'right') {
	        if (soy < toy) {
	            if (sox < tox) {
	                var y$12 = (sy1 + ty0) / 2;
	                if (y$12 < tcy && y$12 > tmy0 && sox < tmx0) {
	                    y$12 = tmy1;
	                }
	                return [
	                    { x: sox, y: y$12 },
	                    { x: tox, y: y$12 },
	                    { x: tox, y: toy }
	                ];
	            }
	            return [{ x: sox, y: toy }];
	        } else {
	            if (sx0 < tox) {
	                var y$13 = Math.max(sy1, ty1) + margin;
	                var x$10 = Math.max(sx1, tx1) + margin;
	                return [
	                    { x: sox, y: y$13 },
	                    { x: x$10, y: y$13 },
	                    { x: x$10, y: toy }
	                ];
	            }
	        }

	        var x$11 = middleOfVerticalSides;

	        return [
	            { x: sox, y: soy },
	            { x: x$11, y: soy },
	            { x: x$11, y: toy }
	        ];
	    } else if (sourceSide === 'bottom' && targetSide === 'left') {
	        if (soy < toy) {
	            if (sox > tox) {
	                var y$14 = (sy1 + ty0) / 2;
	                if (y$14 < tcy && y$14 > tmy0 && sox > tmx1) {
	                    y$14 = tmy1;
	                }
	                return [
	                    { x: sox, y: y$14 },
	                    { x: tox, y: y$14 },
	                    { x: tox, y: toy }
	                ];
	            }
	            return [{ x: sox, y: toy }];
	        } else {
	            if (sx1 > tox) {
	                var y$15 = Math.max(sy1, ty1) + margin;
	                var x$12 = Math.min(sx0, tx0) - margin;
	                return [
	                    { x: sox, y: y$15 },
	                    { x: x$12, y: y$15 },
	                    { x: x$12, y: toy }
	                ];
	            }
	        }

	        var x$13 = middleOfVerticalSides;

	        return [
	            { x: sox, y: soy },
	            { x: x$13, y: soy },
	            { x: x$13, y: toy }
	        ];
	    } else if (sourceSide === 'left' && targetSide === 'bottom') {
	        if (sox > tox && soy >= tmy1) {
	            return [{ x: tox, y: soy }];
	        }

	        if (sox >= tx1 && soy < toy) {
	            var x$14 = (sx1 + tx0) / 2;
	            return [
	                { x: x$14, y: soy },
	                { x: x$14, y: toy },
	                { x: tox, y: toy }
	            ];
	        }

	        if (tox < sx1 && ty1 <= sy0) {
	            var y$16 = (sy0 + ty1) / 2;

	            return [
	                { x: sox, y: soy },
	                { x: sox, y: y$16 },
	                { x: tox, y: y$16 }
	            ];
	        }

	        var x$15 = Math.min(tmx0, sox);
	        var y$17 = Math.max(sy1, ty1) + margin;

	        return [
	            { x: x$15, y: soy },
	            { x: x$15, y: y$17 },
	            { x: tox, y: y$17 }
	        ];
	    } else if (sourceSide === 'left' && targetSide === 'top') {
	        if (sox > tox && soy < tmy0) {
	            return [{ x: tox, y: soy }];
	        }

	        if (sox >= tx1) {
	            if (soy > toy) {
	                var x$16 = (sx0 + tx1) / 2;
	                return [
	                    { x: x$16, y: soy },
	                    { x: x$16, y: toy },
	                    { x: tox, y: toy }
	                ];
	            }
	        }

	        if (tox <= sx1 && toy > soy) {
	            var y$18 = (ty0 + sy1) / 2;

	            return [
	                { x: sox, y: soy },
	                { x: sox, y: y$18 },
	                { x: tox, y: y$18 } ];
	        }

	        var x$17 = toy < soy ? Math.min(sx0, tx0) - margin : smx0;
	        var y$19 = Math.min(sy0, ty0) - margin;

	        return [
	            { x: x$17, y: soy },
	            { x: x$17, y: y$19 },
	            { x: tox, y: y$19 }
	        ];

	    } else if (sourceSide === 'right' && targetSide === 'top') {
	        if (sox < tox && soy < tmy0) {
	            return [{ x: tox, y: soy }];
	        }

	        if (sx1 < tx0 && soy > toy) {
	            var x$18 = (sx1 + tx0) / 2;
	            return [
	                { x: x$18, y: soy },
	                { x: x$18, y: toy },
	                { x: tox, y: toy }
	            ];
	        }

	        if (tox < sox && ty0 > sy1) {
	            var y$20 = (sy1 + ty0) / 2;

	            return [
	                { x: sox, y: soy },
	                { x: sox, y: y$20 },
	                { x: tox, y: y$20 }
	            ];
	        }

	        var x$19 = Math.max(sx1, tx1) + margin;
	        var y$21 = Math.min(sy0, ty0) - margin;
	        return [
	            { x: x$19, y: soy },
	            { x: x$19, y: y$21 },
	            { x: tox, y: y$21 }
	        ];
	    } else if (sourceSide === 'right' && targetSide === 'bottom') {
	        if (sox < tox && soy >= tmy1) {
	            return [{ x: tox, y: soy }];
	        }

	        if (sox <= tx0 && soy < toy) {
	            var x$20 = (sx1 + tx0) / 2;
	            return [
	                { x: x$20, y: soy },
	                { x: x$20, y: toy },
	                { x: tox, y: toy }
	            ];
	        }

	        if (tox > sx0 && ty1 < sy0) {
	            var y$22 = (sy0 + ty1) / 2;

	            return [
	                { x: sox, y: soy },
	                { x: sox, y: y$22 },
	                { x: tox, y: y$22 }
	            ];
	        }

	        var x$21 = Math.max(tmx1, sox);
	        var y$23 = Math.max(sy1, ty1) + margin;

	        return [
	            { x: x$21, y: soy },
	            { x: x$21, y: y$23 },
	            { x: tox, y: y$23 }
	        ];
	    }
	}

	rightAngleRouter.Directions = Directions;

	var rightAngle = rightAngleRouter;



	var routers = ({
		normal: normal,
		oneSide: oneSide,
		orthogonal: orthogonal,
		manhattan: manhattan,
		metro: metro,
		rightAngle: rightAngle
	});

	var CornerTypes = {
	    POINT: 'point',
	    CUBIC: 'cubic',
	    LINE: 'line',
	    GAP: 'gap'
	};

	var DEFINED_CORNER_TYPES = Object.values(CornerTypes);

	var CORNER_RADIUS = 10;
	var PRECISION = 1;

	var straight = function(sourcePoint, targetPoint, routePoints, opt) {
	    if ( routePoints === void 0 ) routePoints = [];
	    if ( opt === void 0 ) opt = {};


	    var cornerType = opt.cornerType; if ( cornerType === void 0 ) cornerType = CornerTypes.POINT;
	    var cornerRadius = opt.cornerRadius; if ( cornerRadius === void 0 ) cornerRadius = CORNER_RADIUS;
	    var cornerPreserveAspectRatio = opt.cornerPreserveAspectRatio; if ( cornerPreserveAspectRatio === void 0 ) cornerPreserveAspectRatio = false;
	    var precision = opt.precision; if ( precision === void 0 ) precision = PRECISION;
	    var raw = opt.raw; if ( raw === void 0 ) raw = false;

	    if (DEFINED_CORNER_TYPES.indexOf(cornerType) === -1) {
	        // unknown `cornerType` provided => error
	        throw new Error('Invalid `cornerType` provided to `straight` connector.');
	    }

	    var path;

	    if ((cornerType === CornerTypes.POINT) || !cornerRadius) {
	        // default option => normal connector
	        // simply connect all points with straight lines
	        var points = [sourcePoint].concat(routePoints).concat([targetPoint]);
	        var polyline = new Polyline(points);
	        path = new Path(polyline);

	    } else {
	        // `cornerType` is not unknown and not 'point' (default) => must be one of other valid types
	        path = new Path();

	        // add initial gap segment = to source point
	        path.appendSegment(Path.createSegment('M', sourcePoint));

	        var nextDistance;
	        var routePointsLength = routePoints.length;
	        for (var i = 0; i < routePointsLength; i++) {

	            var curr = new Point(routePoints[i]);
	            var prev = (routePoints[i - 1] || sourcePoint);
	            var next = (routePoints[i + 1] || targetPoint);
	            var prevDistance = (nextDistance || (curr.distance(prev) / 2)); // try to re-use previously-computed `nextDistance`
	            nextDistance = (curr.distance(next) / 2);

	            var startMove = (void 0), endMove = (void 0);
	            if (!cornerPreserveAspectRatio) {
	                // `startMove` and `endMove` may be different
	                // (this happens when next or previous path point is closer than `2 * cornerRadius`)
	                startMove = -Math.min(cornerRadius, prevDistance);
	                endMove = -Math.min(cornerRadius, nextDistance);
	            } else {
	                // force `startMove` and `endMove` to be the same
	                startMove = endMove = -Math.min(cornerRadius, prevDistance, nextDistance);
	            }

	            // to find `cornerStart` and `cornerEnd`, the logic is as follows (using `cornerStart` as example):
	            // - find a point lying on the line `prev - startMove` such that...
	            // - ...the point lies `abs(startMove)` distance away from `curr`...
	            // - ...and its coordinates are rounded to whole numbers
	            var cornerStart = curr.clone().move(prev, startMove).round(precision);
	            var cornerEnd = curr.clone().move(next, endMove).round(precision);

	            // add in-between straight segment = from previous route point to corner start point
	            // (may have zero length)
	            path.appendSegment(Path.createSegment('L', cornerStart));

	            // add corner segment = from corner start point to corner end point
	            switch (cornerType) {
	                case CornerTypes.CUBIC: {
	                    // corner is rounded
	                    var _13 = (1 / 3);
	                    var _23 = (2 / 3);
	                    var control1 = new Point((_13 * cornerStart.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * cornerStart.y));
	                    var control2 = new Point((_13 * cornerEnd.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * cornerEnd.y));
	                    path.appendSegment(Path.createSegment('C', control1, control2, cornerEnd));
	                    break;
	                }
	                case CornerTypes.LINE: {
	                    // corner has bevel
	                    path.appendSegment(Path.createSegment('L', cornerEnd));
	                    break;
	                }
	                case CornerTypes.GAP: {
	                    // corner has empty space
	                    path.appendSegment(Path.createSegment('M', cornerEnd));
	                    break;
	                }
	                // default: no segment is created
	            }
	        }

	        // add final straight segment = from last corner end point to target point
	        // (= or from start point to end point, if there are no route points)
	        // (may have zero length)
	        path.appendSegment(Path.createSegment('L', targetPoint));
	    }

	    return ((raw) ? path : path.serialize());
	};

	// default size of jump if not specified in options
	var JUMP_SIZE = 5;

	// available jump types
	// first one taken as default
	var JUMP_TYPES = ['arc', 'gap', 'cubic'];

	// default radius
	var RADIUS = 0;

	// takes care of math. error for case when jump is too close to end of line
	var CLOSE_PROXIMITY_PADDING = 1;

	// list of connector types not to jump over.
	var IGNORED_CONNECTORS = ['smooth'];

	// internal constants for round segment
	var _13 = 1 / 3;
	var _23 = 2 / 3;

	/**
	 * Transform start/end and route into series of lines
	 * @param {g.point} sourcePoint start point
	 * @param {g.point} targetPoint end point
	 * @param {g.point[]} route optional list of route
	 * @return {g.line[]} [description]
	 */
	function createLines(sourcePoint, targetPoint, route) {
	    // make a flattened array of all points
	    var points = [].concat(sourcePoint, route, targetPoint);
	    return points.reduce(function(resultLines, point, idx) {
	        // if there is a next point, make a line with it
	        var nextPoint = points[idx + 1];
	        if (nextPoint != null) {
	            resultLines[idx] = line(point, nextPoint);
	        }
	        return resultLines;
	    }, []);
	}

	function setupUpdating(jumpOverLinkView) {
	    var paper = jumpOverLinkView.paper;
	    var updateList = paper._jumpOverUpdateList;

	    // first time setup for this paper
	    if (updateList == null) {
	        updateList = paper._jumpOverUpdateList = [];
	        var graph = paper.model;
	        graph.on('batch:stop', function() {
	            if (this.hasActiveBatch()) { return; }
	            updateJumpOver(paper);
	        });
	        graph.on('reset', function() {
	            updateList = paper._jumpOverUpdateList = [];
	        });
	    }

	    // add this link to a list so it can be updated when some other link is updated
	    if (updateList.indexOf(jumpOverLinkView) < 0) {
	        updateList.push(jumpOverLinkView);

	        // watch for change of connector type or removal of link itself
	        // to remove the link from a list of jump over connectors
	        jumpOverLinkView.listenToOnce(jumpOverLinkView.model, 'change:connector remove', function() {
	            updateList.splice(updateList.indexOf(jumpOverLinkView), 1);
	        });
	    }
	}

	/**
	 * Handler for a batch:stop event to force
	 * update of all registered links with jump over connector
	 * @param {object} batchEvent optional object with info about batch
	 */
	function updateJumpOver(paper) {
	    var updateList = paper._jumpOverUpdateList;
	    for (var i = 0; i < updateList.length; i++) {
	        var linkView = updateList[i];
	        var updateFlag = linkView.getFlag(linkView.constructor.Flags.CONNECTOR);
	        linkView.requestUpdate(updateFlag);
	    }
	}

	/**
	 * Utility function to collect all intersection points of a single
	 * line against group of other lines.
	 * @param {g.line} line where to find points
	 * @param {g.line[]} crossCheckLines lines to cross
	 * @return {g.point[]} list of intersection points
	 */
	function findLineIntersections(line, crossCheckLines) {
	    return toArray(crossCheckLines).reduce(function(res, crossCheckLine) {
	        var intersection = line.intersection(crossCheckLine);
	        if (intersection) {
	            res.push(intersection);
	        }
	        return res;
	    }, []);
	}

	/**
	 * Sorting function for list of points by their distance.
	 * @param {g.point} p1 first point
	 * @param {g.point} p2 second point
	 * @return {number} squared distance between points
	 */
	function sortPoints(p1, p2) {
	    return line(p1, p2).squaredLength();
	}

	/**
	 * Split input line into multiple based on intersection points.
	 * @param {g.line} line input line to split
	 * @param {g.point[]} intersections points where to split the line
	 * @param {number} jumpSize the size of jump arc (length empty spot on a line)
	 * @return {g.line[]} list of lines being split
	 */
	function createJumps(line$1, intersections, jumpSize) {
	    return intersections.reduce(function(resultLines, point$1, idx) {
	        // skipping points that were merged with the previous line
	        // to make bigger arc over multiple lines that are close to each other
	        if (point$1.skip === true) {
	            return resultLines;
	        }

	        // always grab the last line from buffer and modify it
	        var lastLine = resultLines.pop() || line$1;

	        // calculate start and end of jump by moving by a given size of jump
	        var jumpStart = point(point$1).move(lastLine.start, -(jumpSize));
	        var jumpEnd = point(point$1).move(lastLine.start, +(jumpSize));

	        // now try to look at the next intersection point
	        var nextPoint = intersections[idx + 1];
	        if (nextPoint != null) {
	            var distance = jumpEnd.distance(nextPoint);
	            if (distance <= jumpSize) {
	                // next point is close enough, move the jump end by this
	                // difference and mark the next point to be skipped
	                jumpEnd = nextPoint.move(lastLine.start, distance);
	                nextPoint.skip = true;
	            }
	        } else {
	            // this block is inside of `else` as an optimization so the distance is
	            // not calculated when we know there are no other intersection points
	            var endDistance = jumpStart.distance(lastLine.end);
	            // if the end is too close to possible jump, draw remaining line instead of a jump
	            if (endDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) {
	                resultLines.push(lastLine);
	                return resultLines;
	            }
	        }

	        var startDistance = jumpEnd.distance(lastLine.start);
	        if (startDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) {
	            // if the start of line is too close to jump, draw that line instead of a jump
	            resultLines.push(lastLine);
	            return resultLines;
	        }

	        // finally create a jump line
	        var jumpLine = line(jumpStart, jumpEnd);
	        // it's just simple line but with a `isJump` property
	        jumpLine.isJump = true;

	        resultLines.push(
	            line(lastLine.start, jumpStart),
	            jumpLine,
	            line(jumpEnd, lastLine.end)
	        );
	        return resultLines;
	    }, []);
	}

	/**
	 * Assemble `D` attribute of a SVG path by iterating given lines.
	 * @param {g.line[]} lines source lines to use
	 * @param {number} jumpSize the size of jump arc (length empty spot on a line)
	 * @param {number} radius the radius
	 * @return {string}
	 */
	function buildPath(lines, jumpSize, jumpType, radius) {

	    var path = new Path();
	    var segment;

	    // first move to the start of a first line
	    segment = Path.createSegment('M', lines[0].start);
	    path.appendSegment(segment);

	    // make a paths from lines
	    toArray(lines).forEach(function(line, index) {

	        if (line.isJump) {
	            var angle, diff;

	            var control1, control2;

	            if (jumpType === 'arc') { // approximates semicircle with 2 curves
	                angle = -90;
	                // determine rotation of arc based on difference between points
	                diff = line.start.difference(line.end);
	                // make sure the arc always points up (or right)
	                var xAxisRotate = Number((diff.x < 0) || (diff.x === 0 && diff.y < 0));
	                if (xAxisRotate) { angle += 180; }

	                var midpoint = line.midpoint();
	                var centerLine = new Line(midpoint, line.end).rotate(midpoint, angle);

	                var halfLine;

	                // first half
	                halfLine = new Line(line.start, midpoint);

	                control1 = halfLine.pointAt(2 / 3).rotate(line.start, angle);
	                control2 = centerLine.pointAt(1 / 3).rotate(centerLine.end, -angle);

	                segment = Path.createSegment('C', control1, control2, centerLine.end);
	                path.appendSegment(segment);

	                // second half
	                halfLine = new Line(midpoint, line.end);

	                control1 = centerLine.pointAt(1 / 3).rotate(centerLine.end, angle);
	                control2 = halfLine.pointAt(1 / 3).rotate(line.end, -angle);

	                segment = Path.createSegment('C', control1, control2, line.end);
	                path.appendSegment(segment);

	            } else if (jumpType === 'gap') {
	                segment = Path.createSegment('M', line.end);
	                path.appendSegment(segment);

	            } else if (jumpType === 'cubic') { // approximates semicircle with 1 curve
	                angle = line.start.theta(line.end);

	                var xOffset = jumpSize * 0.6;
	                var yOffset = jumpSize * 1.35;

	                // determine rotation of arc based on difference between points
	                diff = line.start.difference(line.end);
	                // make sure the arc always points up (or right)
	                xAxisRotate = Number((diff.x < 0) || (diff.x === 0 && diff.y < 0));
	                if (xAxisRotate) { yOffset *= -1; }

	                control1 = Point(line.start.x + xOffset, line.start.y + yOffset).rotate(line.start, angle);
	                control2 = Point(line.end.x - xOffset, line.end.y + yOffset).rotate(line.end, angle);

	                segment = Path.createSegment('C', control1, control2, line.end);
	                path.appendSegment(segment);
	            }

	        } else {
	            var nextLine = lines[index + 1];
	            if (radius == 0 || !nextLine || nextLine.isJump) {
	                segment = Path.createSegment('L', line.end);
	                path.appendSegment(segment);
	            } else {
	                buildRoundedSegment(radius, path, line.end, line.start, nextLine.end);
	            }
	        }
	    });

	    return path;
	}

	function buildRoundedSegment(offset, path, curr, prev, next) {
	    var prevDistance = curr.distance(prev) / 2;
	    var nextDistance = curr.distance(next) / 2;

	    var startMove = -Math.min(offset, prevDistance);
	    var endMove = -Math.min(offset, nextDistance);

	    var roundedStart = curr.clone().move(prev, startMove).round();
	    var roundedEnd = curr.clone().move(next, endMove).round();

	    var control1 = new Point((_13 * roundedStart.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * roundedStart.y));
	    var control2 = new Point((_13 * roundedEnd.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * roundedEnd.y));

	    var segment;
	    segment = Path.createSegment('L', roundedStart);
	    path.appendSegment(segment);

	    segment = Path.createSegment('C', control1, control2, roundedEnd);
	    path.appendSegment(segment);
	}

	/**
	 * Actual connector function that will be run on every update.
	 * @param {g.point} sourcePoint start point of this link
	 * @param {g.point} targetPoint end point of this link
	 * @param {g.point[]} route of this link
	 * @param {object} opt options
	 * @property {number} size optional size of a jump arc
	 * @return {string} created `D` attribute of SVG path
	 */
	var jumpover = function(sourcePoint, targetPoint, route, opt) { // eslint-disable-line max-params

	    setupUpdating(this);

	    var raw = opt.raw;
	    var jumpSize = opt.size || JUMP_SIZE;
	    var jumpType = opt.jump && ('' + opt.jump).toLowerCase();
	    var radius = opt.radius || RADIUS;
	    var ignoreConnectors = opt.ignoreConnectors || IGNORED_CONNECTORS;

	    // grab the first jump type as a default if specified one is invalid
	    if (JUMP_TYPES.indexOf(jumpType) === -1) {
	        jumpType = JUMP_TYPES[0];
	    }

	    var paper = this.paper;
	    var graph = paper.model;
	    var allLinks = graph.getLinks();

	    // there is just one link, draw it directly
	    if (allLinks.length === 1) {
	        return buildPath(
	            createLines(sourcePoint, targetPoint, route),
	            jumpSize, jumpType, radius
	        );
	    }

	    var thisModel = this.model;
	    var thisIndex = allLinks.indexOf(thisModel);
	    var defaultConnector = paper.options.defaultConnector || {};

	    // not all links are meant to be jumped over.
	    var links = allLinks.filter(function(link, idx) {

	        var connector = link.get('connector') || defaultConnector;

	        // avoid jumping over links with connector type listed in `ignored connectors`.
	        if (toArray(ignoreConnectors).includes(connector.name)) {
	            return false;
	        }
	        // filter out links that are above this one and  have the same connector type
	        // otherwise there would double hoops for each intersection
	        if (idx > thisIndex) {
	            return connector.name !== 'jumpover';
	        }
	        return true;
	    });

	    // find views for all links
	    var linkViews = links.map(function(link) {
	        return paper.findViewByModel(link);
	    });

	    // create lines for this link
	    var thisLines = createLines(
	        sourcePoint,
	        targetPoint,
	        route
	    );

	    // create lines for all other links
	    var linkLines = linkViews.map(function(linkView) {
	        if (linkView == null) {
	            return [];
	        }
	        if (linkView === this) {
	            return thisLines;
	        }
	        return createLines(
	            linkView.sourcePoint,
	            linkView.targetPoint,
	            linkView.route
	        );
	    }, this);

	    // transform lines for this link by splitting with jump lines at
	    // points of intersection with other links
	    var jumpingLines = thisLines.reduce(function(resultLines, thisLine) {
	        // iterate all links and grab the intersections with this line
	        // these are then sorted by distance so the line can be split more easily

	        var intersections = links.reduce(function(res, link, i) {
	            // don't intersection with itself
	            if (link !== thisModel) {

	                var lineIntersections = findLineIntersections(thisLine, linkLines[i]);
	                res.push.apply(res, lineIntersections);
	            }
	            return res;
	        }, []).sort(function(a, b) {
	            return sortPoints(thisLine.start, a) - sortPoints(thisLine.start, b);
	        });

	        if (intersections.length > 0) {
	            // split the line based on found intersection points
	            resultLines.push.apply(resultLines, createJumps(thisLine, intersections, jumpSize));
	        } else {
	            // without any intersection the line goes uninterrupted
	            resultLines.push(thisLine);
	        }
	        return resultLines;
	    }, []);

	    var path = buildPath(jumpingLines, jumpSize, jumpType, radius);
	    return (raw) ? path : path.serialize();
	};

	var normal$1 = function(sourcePoint, targetPoint, route, opt) {
	    if ( route === void 0 ) route = [];
	    if ( opt === void 0 ) opt = {};


	    var raw = opt.raw;
	    var localOpt = {
	        cornerType: 'point',
	        raw: raw
	    };

	    return straight(sourcePoint, targetPoint, route, localOpt);
	};

	var CORNER_RADIUS$1 = 10;
	var PRECISION$1 = 0;

	var rounded = function(sourcePoint, targetPoint, route, opt) {
	    if ( route === void 0 ) route = [];
	    if ( opt === void 0 ) opt = {};


	    var radius = opt.radius; if ( radius === void 0 ) radius = CORNER_RADIUS$1;
	    var raw = opt.raw;
	    var localOpt = {
	        cornerType: 'cubic',
	        cornerRadius: radius,
	        precision: PRECISION$1,
	        raw: raw
	    };

	    return straight(sourcePoint, targetPoint, route, localOpt);
	};

	var smooth = function(sourcePoint, targetPoint, route, opt) {

	    var raw = opt && opt.raw;
	    var path;

	    if (route && route.length !== 0) {

	        var points = [sourcePoint].concat(route).concat([targetPoint]);
	        var curves = Curve.throughPoints(points);

	        path = new Path(curves);

	    } else {
	        // if we have no route, use a default cubic bezier curve
	        // cubic bezier requires two control points
	        // the control points have `x` midway between source and target
	        // this produces an S-like curve

	        path = new Path();

	        var segment;

	        segment = Path.createSegment('M', sourcePoint);
	        path.appendSegment(segment);

	        if ((Math.abs(sourcePoint.x - targetPoint.x)) >= (Math.abs(sourcePoint.y - targetPoint.y))) {
	            var controlPointX = (sourcePoint.x + targetPoint.x) / 2;

	            segment = Path.createSegment('C', controlPointX, sourcePoint.y, controlPointX, targetPoint.y, targetPoint.x, targetPoint.y);
	            path.appendSegment(segment);

	        } else {
	            var controlPointY = (sourcePoint.y + targetPoint.y) / 2;

	            segment = Path.createSegment('C', sourcePoint.x, controlPointY, targetPoint.x, controlPointY, targetPoint.x, targetPoint.y);
	            path.appendSegment(segment);

	        }
	    }

	    return (raw) ? path : path.serialize();
	};

	var Directions$1 = {
	    AUTO: 'auto',
	    HORIZONTAL: 'horizontal',
	    VERTICAL: 'vertical',
	    CLOSEST_POINT: 'closest-point',
	    OUTWARDS: 'outwards'
	};

	var TangentDirections = {
	    UP: 'up',
	    DOWN: 'down',
	    LEFT: 'left',
	    RIGHT: 'right',
	    AUTO: 'auto',
	    CLOSEST_POINT: 'closest-point',
	    OUTWARDS: 'outwards'
	};

	var curve = function(sourcePoint, targetPoint, route, opt, linkView) {
	    if ( route === void 0 ) route = [];
	    if ( opt === void 0 ) opt = {};

	    var raw = Boolean(opt.raw);
	    // distanceCoefficient - a coefficient of the tangent vector length relative to the distance between points.
	    // angleTangentCoefficient - a coefficient of the end tangents length in the case of angles larger than 45 degrees.
	    // tension - a Catmull-Rom curve tension parameter.
	    // sourceTangent - a tangent vector along the curve at the sourcePoint.
	    // sourceDirection - a unit direction vector along the curve at the sourcePoint.
	    // targetTangent - a tangent vector along the curve at the targetPoint.
	    // targetDirection - a unit direction vector along the curve at the targetPoint.
	    // precision - a rounding precision for path values.
	    var direction = opt.direction; if ( direction === void 0 ) direction = Directions$1.AUTO;
	    var precision = opt.precision; if ( precision === void 0 ) precision = 3;
	    var options = {
	        coeff: opt.distanceCoefficient || 0.6,
	        angleTangentCoefficient: opt.angleTangentCoefficient || 80,
	        tau: opt.tension || 0.5,
	        sourceTangent: opt.sourceTangent ? new Point(opt.sourceTangent) : null,
	        targetTangent: opt.targetTangent ? new Point(opt.targetTangent) : null,
	        rotate: Boolean(opt.rotate)
	    };
	    if (typeof opt.sourceDirection === 'string')
	        { options.sourceDirection = opt.sourceDirection; }
	    else if (typeof opt.sourceDirection === 'number')
	        { options.sourceDirection = new Point(1, 0).rotate(null, opt.sourceDirection); }
	    else
	        { options.sourceDirection = opt.sourceDirection ? new Point(opt.sourceDirection).normalize() : null; }

	    if (typeof opt.targetDirection === 'string')
	        { options.targetDirection = opt.targetDirection; }
	    else if (typeof opt.targetDirection === 'number')
	        { options.targetDirection = new Point(1, 0).rotate(null, opt.targetDirection); }
	    else
	        { options.targetDirection = opt.targetDirection ? new Point(opt.targetDirection).normalize() : null; }

	    var completeRoute = [sourcePoint ].concat( route, [targetPoint]).map(function (p) { return new Point(p); });

	    // The calculation of a sourceTangent
	    var sourceTangent;
	    if (options.sourceTangent) {
	        sourceTangent = options.sourceTangent;
	    } else {
	        var sourceDirection = getSourceTangentDirection(linkView, completeRoute, direction, options);
	        var tangentLength = completeRoute[0].distance(completeRoute[1]) * options.coeff;
	        var pointsVector = completeRoute[1].difference(completeRoute[0]).normalize();
	        var angle = angleBetweenVectors(sourceDirection, pointsVector);
	        if (angle > Math.PI / 4) {
	            var updatedLength = tangentLength + (angle - Math.PI / 4) * options.angleTangentCoefficient;
	            sourceTangent = sourceDirection.clone().scale(updatedLength, updatedLength);
	        } else {
	            sourceTangent = sourceDirection.clone().scale(tangentLength, tangentLength);
	        }
	    }

	    // The calculation of a targetTangent
	    var targetTangent;
	    if (options.targetTangent) {
	        targetTangent = options.targetTangent;
	    } else {
	        var targetDirection = getTargetTangentDirection(linkView, completeRoute, direction, options);
	        var last = completeRoute.length - 1;
	        var tangentLength$1 = completeRoute[last - 1].distance(completeRoute[last]) * options.coeff;
	        var pointsVector$1 = completeRoute[last - 1].difference(completeRoute[last]).normalize();
	        var angle$1 = angleBetweenVectors(targetDirection, pointsVector$1);
	        if (angle$1 > Math.PI / 4) {
	            var updatedLength$1 = tangentLength$1 + (angle$1 - Math.PI / 4) * options.angleTangentCoefficient;
	            targetTangent = targetDirection.clone().scale(updatedLength$1, updatedLength$1);
	        } else {
	            targetTangent = targetDirection.clone().scale(tangentLength$1, tangentLength$1);
	        }
	    }

	    var catmullRomCurves = createCatmullRomCurves(completeRoute, sourceTangent, targetTangent, options);
	    var bezierCurves = catmullRomCurves.map(function (curve) { return catmullRomToBezier(curve, options); });
	    var path = new Path(bezierCurves).round(precision);

	    return (raw) ? path : path.serialize();
	};
	curve.Directions = Directions$1;
	curve.TangentDirections = TangentDirections;

	function getHorizontalSourceDirection(linkView, route, options) {
	    var sourceBBox = linkView.sourceBBox;

	    var sourceSide;
	    var rotation;
	    if (!linkView.sourceView) {
	        if (sourceBBox.x > route[1].x)
	            { sourceSide = 'right'; }
	        else
	            { sourceSide = 'left'; }
	    } else {
	        rotation = linkView.sourceView.model.angle();
	        if (options.rotate && rotation) {
	            var unrotatedBBox = linkView.sourceView.getNodeUnrotatedBBox(linkView.sourceView.el);
	            var sourcePoint = route[0].clone();
	            sourcePoint.rotate(sourceBBox.center(), rotation);
	            sourceSide = unrotatedBBox.sideNearestToPoint(sourcePoint);
	        } else {
	            sourceSide = sourceBBox.sideNearestToPoint(route[0]);
	        }
	    }

	    var direction;
	    switch (sourceSide) {
	        case 'left':
	            direction = new Point(-1, 0);
	            break;
	        case 'right':
	        default:
	            direction = new Point(1, 0);
	            break;
	    }

	    if (options.rotate && rotation) {
	        direction.rotate(null, -rotation);
	    }

	    return direction;
	}

	function getHorizontalTargetDirection(linkView, route, options) {
	    var targetBBox = linkView.targetBBox;

	    var targetSide;
	    var rotation;
	    if (!linkView.targetView) {
	        if (targetBBox.x > route[route.length - 2].x)
	            { targetSide = 'left'; }
	        else
	            { targetSide = 'right'; }
	    } else {
	        rotation = linkView.targetView.model.angle();
	        if (options.rotate && rotation) {
	            var unrotatedBBox = linkView.targetView.getNodeUnrotatedBBox(linkView.targetView.el);
	            var targetPoint = route[route.length - 1].clone();
	            targetPoint.rotate(targetBBox.center(), rotation);
	            targetSide = unrotatedBBox.sideNearestToPoint(targetPoint);
	        } else {
	            targetSide = targetBBox.sideNearestToPoint(route[route.length - 1]);
	        }
	    }

	    var direction;
	    switch (targetSide) {
	        case 'left':
	            direction = new Point(-1, 0);
	            break;
	        case 'right':
	        default:
	            direction = new Point(1, 0);
	            break;
	    }

	    if (options.rotate && rotation) {
	        direction.rotate(null, -rotation);
	    }

	    return direction;
	}

	function getVerticalSourceDirection(linkView, route, options) {
	    var sourceBBox = linkView.sourceBBox;

	    var sourceSide;
	    var rotation;
	    if (!linkView.sourceView) {
	        if (sourceBBox.y > route[1].y)
	            { sourceSide = 'bottom'; }
	        else
	            { sourceSide = 'top'; }
	    } else {
	        rotation = linkView.sourceView.model.angle();
	        if (options.rotate && rotation) {
	            var unrotatedBBox = linkView.sourceView.getNodeUnrotatedBBox(linkView.sourceView.el);
	            var sourcePoint = route[0].clone();
	            sourcePoint.rotate(sourceBBox.center(), rotation);
	            sourceSide = unrotatedBBox.sideNearestToPoint(sourcePoint);
	        } else {
	            sourceSide = sourceBBox.sideNearestToPoint(route[0]);
	        }
	    }

	    var direction;
	    switch (sourceSide) {
	        case 'top':
	            direction = new Point(0, -1);
	            break;
	        case 'bottom':
	        default:
	            direction = new Point(0, 1);
	            break;
	    }

	    if (options.rotate && rotation) {
	        direction.rotate(null, -rotation);
	    }

	    return direction;
	}

	function getVerticalTargetDirection(linkView, route, options) {
	    var targetBBox = linkView.targetBBox;

	    var targetSide;
	    var rotation;
	    if (!linkView.targetView) {
	        if (targetBBox.y > route[route.length - 2].y)
	            { targetSide = 'top'; }
	        else
	            { targetSide = 'bottom'; }
	    } else {
	        rotation = linkView.targetView.model.angle();
	        if (options.rotate && rotation) {
	            var unrotatedBBox = linkView.targetView.getNodeUnrotatedBBox(linkView.targetView.el);
	            var targetPoint = route[route.length - 1].clone();
	            targetPoint.rotate(targetBBox.center(), rotation);
	            targetSide = unrotatedBBox.sideNearestToPoint(targetPoint);
	        } else {
	            targetSide = targetBBox.sideNearestToPoint(route[route.length - 1]);
	        }
	    }


	    var direction;
	    switch (targetSide) {
	        case 'top':
	            direction = new Point(0, -1);
	            break;
	        case 'bottom':
	        default:
	            direction = new Point(0, 1);
	            break;
	    }

	    if (options.rotate && rotation) {
	        direction.rotate(null, -rotation);
	    }

	    return direction;
	}

	function getAutoSourceDirection(linkView, route, options) {
	    var sourceBBox = linkView.sourceBBox;

	    var sourceSide;
	    var rotation;
	    if (!linkView.sourceView) {
	        sourceSide = sourceBBox.sideNearestToPoint(route[1]);
	    } else {
	        rotation = linkView.sourceView.model.angle();
	        if (options.rotate && rotation) {
	            var unrotatedBBox = linkView.sourceView.getNodeUnrotatedBBox(linkView.sourceView.el);
	            var sourcePoint = route[0].clone();
	            sourcePoint.rotate(sourceBBox.center(), rotation);
	            sourceSide = unrotatedBBox.sideNearestToPoint(sourcePoint);
	        } else {
	            sourceSide = sourceBBox.sideNearestToPoint(route[0]);
	        }
	    }

	    var direction;
	    switch (sourceSide) {
	        case 'top':
	            direction = new Point(0, -1);
	            break;
	        case 'bottom':
	            direction = new Point(0, 1);
	            break;
	        case 'right':
	            direction = new Point(1, 0);
	            break;
	        case 'left':
	            direction = new Point(-1, 0);
	            break;
	    }

	    if (options.rotate && rotation) {
	        direction.rotate(null, -rotation);
	    }

	    return direction;
	}

	function getAutoTargetDirection(linkView, route, options) {
	    var targetBBox = linkView.targetBBox;

	    var targetSide;
	    var rotation;
	    if (!linkView.targetView) {
	        targetSide = targetBBox.sideNearestToPoint(route[route.length - 2]);
	    } else {
	        rotation = linkView.targetView.model.angle();
	        if (options.rotate && rotation) {
	            var unrotatedBBox = linkView.targetView.getNodeUnrotatedBBox(linkView.targetView.el);
	            var targetPoint = route[route.length - 1].clone();
	            targetPoint.rotate(targetBBox.center(), rotation);
	            targetSide = unrotatedBBox.sideNearestToPoint(targetPoint);
	        } else {
	            targetSide = targetBBox.sideNearestToPoint(route[route.length - 1]);
	        }
	    }

	    var direction;
	    switch (targetSide) {
	        case 'top':
	            direction = new Point(0, -1);
	            break;
	        case 'bottom':
	            direction = new Point(0, 1);
	            break;
	        case 'right':
	            direction = new Point(1, 0);
	            break;
	        case 'left':
	            direction = new Point(-1, 0);
	            break;
	    }

	    if (options.rotate && rotation) {
	        direction.rotate(null, -rotation);
	    }

	    return direction;
	}

	function getClosestPointSourceDirection(linkView, route, options) {
	    return route[1].difference(route[0]).normalize();
	}

	function getClosestPointTargetDirection(linkView, route, options) {
	    var last = route.length - 1;
	    return route[last - 1].difference(route[last]).normalize();
	}

	function getOutwardsSourceDirection(linkView, route, options) {
	    var sourceBBox = linkView.sourceBBox;
	    var sourceCenter = sourceBBox.center();
	    return route[0].difference(sourceCenter).normalize();
	}

	function getOutwardsTargetDirection(linkView, route, options) {
	    var targetBBox = linkView.targetBBox;
	    var targetCenter = targetBBox.center();
	    return route[route.length - 1].difference(targetCenter).normalize();
	}

	function getSourceTangentDirection(linkView, route, direction, options) {
	    if (options.sourceDirection) {
	        switch (options.sourceDirection) {
	            case TangentDirections.UP:
	                return new Point(0, -1);
	            case TangentDirections.DOWN:
	                return new Point(0, 1);
	            case TangentDirections.LEFT:
	                return new Point(-1, 0);
	            case TangentDirections.RIGHT:
	                return new Point(1, 0);
	            case TangentDirections.AUTO:
	                return getAutoSourceDirection(linkView, route, options);
	            case TangentDirections.CLOSEST_POINT:
	                return getClosestPointSourceDirection(linkView, route, options);
	            case TangentDirections.OUTWARDS:
	                return getOutwardsSourceDirection(linkView, route, options);
	            default:
	                return options.sourceDirection;
	        }
	    }

	    switch (direction) {
	        case Directions$1.HORIZONTAL:
	            return getHorizontalSourceDirection(linkView, route, options);
	        case Directions$1.VERTICAL:
	            return getVerticalSourceDirection(linkView, route, options);
	        case Directions$1.CLOSEST_POINT:
	            return getClosestPointSourceDirection(linkView, route, options);
	        case Directions$1.OUTWARDS:
	            return getOutwardsSourceDirection(linkView, route, options);
	        case Directions$1.AUTO:
	        default:
	            return getAutoSourceDirection(linkView, route, options);
	    }
	}

	function getTargetTangentDirection(linkView, route, direction, options) {
	    if (options.targetDirection) {
	        switch (options.targetDirection) {
	            case TangentDirections.UP:
	                return new Point(0, -1);
	            case TangentDirections.DOWN:
	                return new Point(0, 1);
	            case TangentDirections.LEFT:
	                return new Point(-1, 0);
	            case TangentDirections.RIGHT:
	                return new Point(0, 1);
	            case TangentDirections.AUTO:
	                return getAutoTargetDirection(linkView, route, options);
	            case TangentDirections.CLOSEST_POINT:
	                return getClosestPointTargetDirection(linkView, route, options);
	            case TangentDirections.OUTWARDS:
	                return getOutwardsTargetDirection(linkView, route, options);
	            default:
	                return options.targetDirection;
	        }
	    }

	    switch (direction) {
	        case Directions$1.HORIZONTAL:
	            return getHorizontalTargetDirection(linkView, route, options);
	        case Directions$1.VERTICAL:
	            return getVerticalTargetDirection(linkView, route, options);
	        case Directions$1.CLOSEST_POINT:
	            return getClosestPointTargetDirection(linkView, route, options);
	        case Directions$1.OUTWARDS:
	            return getOutwardsTargetDirection(linkView, route, options);
	        case Directions$1.AUTO:
	        default:
	            return getAutoTargetDirection(linkView, route, options);
	    }
	}

	function rotateVector(vector, angle) {
	    var cos = Math.cos(angle);
	    var sin = Math.sin(angle);
	    var x = cos * vector.x - sin * vector.y;
	    var y = sin * vector.x + cos * vector.y;
	    vector.x = x;
	    vector.y = y;
	}

	function angleBetweenVectors(v1, v2) {
	    var cos = v1.dot(v2) / (v1.magnitude() * v2.magnitude());
	    if (cos < -1) { cos = -1; }
	    if (cos > 1) { cos = 1; }
	    return Math.acos(cos);
	}

	function determinant(v1, v2) {
	    return v1.x * v2.y - v1.y * v2.x;
	}

	function createCatmullRomCurves(points, sourceTangent, targetTangent, options) {
	    var tau = options.tau;
	    var coeff = options.coeff;
	    var distances = [];
	    var tangents = [];
	    var catmullRomCurves = [];
	    var n = points.length - 1;

	    for (var i = 0; i < n; i++) {
	        distances[i] = points[i].distance(points[i + 1]);
	    }

	    tangents[0] = sourceTangent;
	    tangents[n] = targetTangent;

	    // The calculation of tangents of vertices
	    for (var i$1 = 1; i$1 < n; i$1++) {
	        var tpPrev = (void 0);
	        var tpNext = (void 0);
	        if (i$1 === 1) {
	            tpPrev = points[i$1 - 1].clone().offset(tangents[i$1 - 1].x, tangents[i$1 - 1].y);
	        } else {
	            tpPrev = points[i$1 - 1].clone();
	        }
	        if (i$1 === n - 1) {
	            tpNext = points[i$1 + 1].clone().offset(tangents[i$1 + 1].x, tangents[i$1 + 1].y);
	        } else {
	            tpNext = points[i$1 + 1].clone();
	        }
	        var v1 = tpPrev.difference(points[i$1]).normalize();
	        var v2 = tpNext.difference(points[i$1]).normalize();
	        var vAngle = angleBetweenVectors(v1, v2);

	        var rot = (Math.PI - vAngle) / 2;
	        var t = (void 0);
	        var vectorDeterminant = determinant(v1, v2);
	        var pointsDeterminant = (void 0);
	        pointsDeterminant = determinant(points[i$1].difference(points[i$1 + 1]), points[i$1].difference(points[i$1 - 1]));
	        if (vectorDeterminant < 0) {
	            rot = -rot;
	        }
	        if ((vAngle < Math.PI / 2) && ((rot < 0 && pointsDeterminant < 0) || (rot > 0 && pointsDeterminant > 0))) {
	            rot = rot - Math.PI;
	        }
	        t = v2.clone();
	        rotateVector(t, rot);

	        var t1 = t.clone();
	        var t2 = t.clone();
	        var scaleFactor1 = distances[i$1 - 1] * coeff;
	        var scaleFactor2 = distances[i$1] * coeff;
	        t1.scale(scaleFactor1, scaleFactor1);
	        t2.scale(scaleFactor2, scaleFactor2);

	        tangents[i$1] = [t1, t2];
	    }

	    // The building of a Catmull-Rom curve based of tangents of points
	    for (var i$2 = 0; i$2 < n; i$2++) {
	        var p0 = (void 0);
	        var p3 = (void 0);
	        if (i$2 === 0) {
	            p0 = points[i$2 + 1].difference(tangents[i$2].x / tau, tangents[i$2].y / tau);
	        } else {
	            p0 = points[i$2 + 1].difference(tangents[i$2][1].x / tau, tangents[i$2][1].y / tau);
	        }
	        if (i$2 === n - 1) {
	            p3 = points[i$2].clone().offset(tangents[i$2 + 1].x / tau, tangents[i$2 + 1].y / tau);
	        } else {
	            p3 = points[i$2].difference(tangents[i$2 + 1][0].x / tau, tangents[i$2 + 1][0].y / tau);
	        }

	        catmullRomCurves[i$2] = [p0, points[i$2], points[i$2 + 1], p3];
	    }
	    return catmullRomCurves;
	}

	// The function to convert Catmull-Rom curve to Bezier curve using the tension (tau)
	function catmullRomToBezier(points, options) {
	    var tau = options.tau;

	    var bcp1 = new Point();
	    bcp1.x = points[1].x + (points[2].x - points[0].x) / (6 * tau);
	    bcp1.y = points[1].y + (points[2].y - points[0].y) / (6 * tau);

	    var bcp2 = new Point();
	    bcp2.x = points[2].x + (points[3].x - points[1].x) / (6 * tau);
	    bcp2.y = points[2].y + (points[3].y - points[1].y) / (6 * tau);
	    return new Curve(
	        points[1],
	        bcp1,
	        bcp2,
	        points[2]
	    );
	}



	var connectors = ({
		straight: straight,
		jumpover: jumpover,
		normal: normal$1,
		rounded: rounded,
		smooth: smooth,
		curve: curve
	});

	var Flags$2 = {
	    TOOLS: CellView.Flags.TOOLS,
	    RENDER: 'RENDER',
	    UPDATE: 'UPDATE',
	    LEGACY_TOOLS: 'LEGACY_TOOLS',
	    LABELS: 'LABELS',
	    VERTICES: 'VERTICES',
	    SOURCE: 'SOURCE',
	    TARGET: 'TARGET',
	    CONNECTOR: 'CONNECTOR'
	};

	// Link base view and controller.
	// ----------------------------------------

	var LinkView = CellView.extend({

	    className: function() {

	        var classNames = CellView.prototype.className.apply(this).split(' ');

	        classNames.push('link');

	        return classNames.join(' ');
	    },

	    options: {

	        shortLinkLength: 105,
	        doubleLinkTools: false,
	        longLinkLength: 155,
	        linkToolsOffset: 40,
	        doubleLinkToolsOffset: 65,
	        sampleInterval: 50
	    },

	    _labelCache: null,
	    _labelSelectors: null,
	    _markerCache: null,
	    _V: null,
	    _dragData: null, // deprecated

	    metrics: null,
	    decimalsRounding: 2,

	    initialize: function() {

	        CellView.prototype.initialize.apply(this, arguments);

	        // `_.labelCache` is a mapping of indexes of labels in the `this.get('labels')` array to
	        // `<g class="label">` nodes wrapped by Vectorizer. This allows for quick access to the
	        // nodes in `updateLabelPosition()` in order to update the label positions.
	        this._labelCache = {};

	        // a cache of label selectors
	        this._labelSelectors = {};

	        // keeps markers bboxes and positions again for quicker access
	        this._markerCache = {};

	        // cache of default markup nodes
	        this._V = {};

	        // connection path metrics
	        this.cleanNodesCache();
	    },

	    presentationAttributes: {
	        markup: [Flags$2.RENDER],
	        attrs: [Flags$2.UPDATE],
	        router: [Flags$2.UPDATE],
	        connector: [Flags$2.CONNECTOR],
	        smooth: [Flags$2.UPDATE],
	        manhattan: [Flags$2.UPDATE],
	        toolMarkup: [Flags$2.LEGACY_TOOLS],
	        labels: [Flags$2.LABELS],
	        labelMarkup: [Flags$2.LABELS],
	        vertices: [Flags$2.VERTICES, Flags$2.UPDATE],
	        vertexMarkup: [Flags$2.VERTICES],
	        source: [Flags$2.SOURCE, Flags$2.UPDATE],
	        target: [Flags$2.TARGET, Flags$2.UPDATE]
	    },

	    initFlag: [Flags$2.RENDER, Flags$2.SOURCE, Flags$2.TARGET, Flags$2.TOOLS],

	    UPDATE_PRIORITY: 1,

	    confirmUpdate: function(flags, opt) {

	        opt || (opt = {});

	        if (this.hasFlag(flags, Flags$2.SOURCE)) {
	            if (!this.updateEndProperties('source')) { return flags; }
	            flags = this.removeFlag(flags, Flags$2.SOURCE);
	        }

	        if (this.hasFlag(flags, Flags$2.TARGET)) {
	            if (!this.updateEndProperties('target')) { return flags; }
	            flags = this.removeFlag(flags, Flags$2.TARGET);
	        }

	        var ref = this;
	        var paper = ref.paper;
	        var sourceView = ref.sourceView;
	        var targetView = ref.targetView;
	        if (paper && ((sourceView && !paper.isViewMounted(sourceView)) || (targetView && !paper.isViewMounted(targetView)))) {
	            // Wait for the sourceView and targetView to be rendered
	            return flags;
	        }

	        if (this.hasFlag(flags, Flags$2.RENDER)) {
	            this.render();
	            this.updateHighlighters(true);
	            this.updateTools(opt);
	            flags = this.removeFlag(flags, [Flags$2.RENDER, Flags$2.UPDATE, Flags$2.VERTICES, Flags$2.LABELS, Flags$2.TOOLS, Flags$2.LEGACY_TOOLS, Flags$2.CONNECTOR]);
	            return flags;
	        }

	        var updateHighlighters = false;

	        if (this.hasFlag(flags, Flags$2.VERTICES)) {
	            this.renderVertexMarkers();
	            flags = this.removeFlag(flags, Flags$2.VERTICES);
	        }

	        var ref$1 = this;
	        var model = ref$1.model;
	        var attributes = model.attributes;
	        var updateLabels = this.hasFlag(flags, Flags$2.LABELS);
	        var updateLegacyTools = this.hasFlag(flags, Flags$2.LEGACY_TOOLS);

	        if (updateLabels) {
	            this.onLabelsChange(model, attributes.labels, opt);
	            flags = this.removeFlag(flags, Flags$2.LABELS);
	            updateHighlighters = true;
	        }

	        if (updateLegacyTools) {
	            this.renderTools();
	            flags = this.removeFlag(flags, Flags$2.LEGACY_TOOLS);
	        }

	        var updateAll = this.hasFlag(flags, Flags$2.UPDATE);
	        var updateConnector = this.hasFlag(flags, Flags$2.CONNECTOR);
	        if (updateAll || updateConnector) {
	            if (!updateAll) {
	                // Keep the current route and update the geometry
	                this.updatePath();
	                this.updateDOM();
	            } else if (opt.translateBy && model.isRelationshipEmbeddedIn(opt.translateBy)) {
	                // The link is being translated by an ancestor that will
	                // shift source point, target point and all vertices
	                // by an equal distance.
	                this.translate(opt.tx, opt.ty);
	            } else {
	                this.update();
	            }
	            this.updateTools(opt);
	            flags = this.removeFlag(flags, [Flags$2.UPDATE, Flags$2.TOOLS, Flags$2.CONNECTOR]);
	            updateLabels = false;
	            updateLegacyTools = false;
	            updateHighlighters = true;
	        }

	        if (updateLabels) {
	            this.updateLabelPositions();
	        }

	        if (updateLegacyTools) {
	            this.updateToolsPosition();
	        }

	        if (updateHighlighters) {
	            this.updateHighlighters();
	        }

	        if (this.hasFlag(flags, Flags$2.TOOLS)) {
	            this.updateTools(opt);
	            flags = this.removeFlag(flags, Flags$2.TOOLS);
	        }

	        return flags;
	    },

	    requestConnectionUpdate: function(opt) {
	        this.requestUpdate(this.getFlag(Flags$2.UPDATE), opt);
	    },

	    isLabelsRenderRequired: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        var previousLabels = this.model.previous('labels');
	        if (!previousLabels) { return true; }

	        // Here is an optimization for cases when we know, that change does
	        // not require re-rendering of all labels.
	        if (('propertyPathArray' in opt) && ('propertyValue' in opt)) {
	            // The label is setting by `prop()` method
	            var pathArray = opt.propertyPathArray || [];
	            var pathLength = pathArray.length;
	            if (pathLength > 1) {
	                // We are changing a single label here e.g. 'labels/0/position'
	                var labelExists = !!previousLabels[pathArray[1]];
	                if (labelExists) {
	                    if (pathLength === 2) {
	                        // We are changing the entire label. Need to check if the
	                        // markup is also being changed.
	                        return ('markup' in Object(opt.propertyValue));
	                    } else if (pathArray[2] !== 'markup') {
	                        // We are changing a label property but not the markup
	                        return false;
	                    }
	                }
	            }
	        }

	        return true;
	    },

	    onLabelsChange: function(_link, _labels, opt) {

	        // Note: this optimization works in async=false mode only
	        if (this.isLabelsRenderRequired(opt)) {
	            this.renderLabels();
	        } else {
	            this.updateLabels();
	        }
	    },

	    // Rendering.
	    // ----------

	    render: function() {

	        this.vel.empty();
	        this.unmountLabels();
	        this._V = {};
	        this.renderMarkup();
	        // rendering labels has to be run after the link is appended to DOM tree. (otherwise <Text> bbox
	        // returns zero values)
	        this.renderLabels();
	        this.update();

	        return this;
	    },

	    renderMarkup: function() {

	        var link = this.model;
	        var markup = link.get('markup') || link.markup;
	        if (!markup) { throw new Error('dia.LinkView: markup required'); }
	        if (Array.isArray(markup)) { return this.renderJSONMarkup(markup); }
	        if (typeof markup === 'string') { return this.renderStringMarkup(markup); }
	        throw new Error('dia.LinkView: invalid markup');
	    },

	    renderJSONMarkup: function(markup) {

	        var doc = this.parseDOMJSON(markup, this.el);
	        // Selectors
	        this.selectors = doc.selectors;
	        // Fragment
	        this.vel.append(doc.fragment);
	    },

	    renderStringMarkup: function(markup) {

	        // A special markup can be given in the `properties.markup` property. This might be handy
	        // if e.g. arrowhead markers should be `<image>` elements or any other element than `<path>`s.
	        // `.connection`, `.connection-wrap`, `.marker-source` and `.marker-target` selectors
	        // of elements with special meaning though. Therefore, those classes should be preserved in any
	        // special markup passed in `properties.markup`.
	        var children = V(markup);
	        // custom markup may contain only one children
	        if (!Array.isArray(children)) { children = [children]; }
	        // Cache all children elements for quicker access.
	        var cache = this._V; // vectorized markup;
	        for (var i = 0, n = children.length; i < n; i++) {
	            var child = children[i];
	            var className = child.attr('class');
	            if (className) {
	                // Strip the joint class name prefix, if there is one.
	                className = removeClassNamePrefix(className);
	                cache[$.camelCase(className)] = child;
	            }
	        }
	        // partial rendering
	        this.renderTools();
	        this.renderVertexMarkers();
	        this.renderArrowheadMarkers();
	        this.vel.append(children);
	    },

	    _getLabelMarkup: function(labelMarkup) {

	        if (!labelMarkup) { return undefined; }

	        if (Array.isArray(labelMarkup)) { return this.parseDOMJSON(labelMarkup, null); }
	        if (typeof labelMarkup === 'string') { return this._getLabelStringMarkup(labelMarkup); }
	        throw new Error('dia.linkView: invalid label markup');
	    },

	    _getLabelStringMarkup: function(labelMarkup) {

	        var children = V(labelMarkup);
	        var fragment = document.createDocumentFragment();

	        if (!Array.isArray(children)) {
	            fragment.appendChild(children.node);

	        } else {
	            for (var i = 0, n = children.length; i < n; i++) {
	                var currentChild = children[i].node;
	                fragment.appendChild(currentChild);
	            }
	        }

	        return { fragment: fragment, selectors: {}}; // no selectors
	    },

	    // Label markup fragment may come wrapped in <g class="label" />, or not.
	    // If it doesn't, add the <g /> container here.
	    _normalizeLabelMarkup: function(markup) {

	        if (!markup) { return undefined; }

	        var fragment = markup.fragment;
	        if (!(markup.fragment instanceof DocumentFragment) || !markup.fragment.hasChildNodes()) { throw new Error('dia.LinkView: invalid label markup.'); }

	        var vNode;
	        var childNodes = fragment.childNodes;

	        if ((childNodes.length > 1) || childNodes[0].nodeName.toUpperCase() !== 'G') {
	            // default markup fragment is not wrapped in <g />
	            // add a <g /> container
	            vNode = V('g').append(fragment);
	        } else {
	            vNode = V(childNodes[0]);
	        }

	        vNode.addClass('label');

	        return { node: vNode.node, selectors: markup.selectors };
	    },

	    renderLabels: function() {

	        var cache = this._V;
	        var vLabels = cache.labels;
	        var labelCache = this._labelCache = {};
	        var labelSelectors = this._labelSelectors = {};
	        var model = this.model;
	        var labels = model.attributes.labels || [];
	        var labelsCount = labels.length;

	        if (labelsCount === 0) {
	            if (vLabels) { vLabels.remove(); }
	            return this;
	        }

	        if (vLabels) {
	            vLabels.empty();
	        }  else {
	            // there is no label container in the markup but some labels are defined
	            // add a <g class="labels" /> container
	            vLabels = cache.labels = V('g').addClass('labels');
	            if (this.options.labelsLayer) {
	                vLabels.addClass(addClassNamePrefix(result(this, 'className')));
	                vLabels.attr('model-id', model.id);
	            }
	        }

	        for (var i = 0; i < labelsCount; i++) {

	            var label = labels[i];
	            var labelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(label.markup));
	            var labelNode;
	            var selectors;
	            if (labelMarkup) {

	                labelNode = labelMarkup.node;
	                selectors = labelMarkup.selectors;

	            } else {

	                var builtinDefaultLabel =  model._builtins.defaultLabel;
	                var builtinDefaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(builtinDefaultLabel.markup));
	                var defaultLabel = model._getDefaultLabel();
	                var defaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(defaultLabel.markup));
	                var defaultMarkup = defaultLabelMarkup || builtinDefaultLabelMarkup;

	                labelNode = defaultMarkup.node;
	                selectors = defaultMarkup.selectors;
	            }

	            labelNode.setAttribute('label-idx', i); // assign label-idx
	            vLabels.append(labelNode);
	            labelCache[i] = labelNode; // cache node for `updateLabels()` so it can just update label node positions

	            var rootSelector = this.selector;
	            if (selectors[rootSelector]) { throw new Error('dia.LinkView: ambiguous label root selector.'); }
	            selectors[rootSelector] = labelNode;

	            labelSelectors[i] = selectors; // cache label selectors for `updateLabels()`
	        }
	        if (!vLabels.parent()) {
	            this.mountLabels();
	        }

	        this.updateLabels();

	        return this;
	    },

	    mountLabels: function() {
	        var ref = this;
	        var el = ref.el;
	        var paper = ref.paper;
	        var model = ref.model;
	        var _V = ref._V;
	        var options = ref.options;
	        var vLabels = _V.labels;
	        if (!vLabels || !model.hasLabels()) { return; }
	        var node = vLabels.node;
	        if (options.labelsLayer) {
	            paper.getLayerView(options.labelsLayer).insertSortedNode(node, model.get('z'));
	        } else {
	            if (node.parentNode !== el) {
	                el.appendChild(node);
	            }
	        }
	    },

	    unmountLabels: function() {
	        var ref = this;
	        var options = ref.options;
	        var _V = ref._V;
	        if (!_V) { return; }
	        var vLabels = _V.labels;
	        if (vLabels && options.labelsLayer) {
	            vLabels.remove();
	        }
	    },

	    findLabelNode: function(labelIndex, selector) {
	        var labelRoot = this._labelCache[labelIndex];
	        if (!labelRoot) { return null; }
	        var labelSelectors = this._labelSelectors[labelIndex];
	        var ref = this.findBySelector(selector, labelRoot, labelSelectors);
	        var node = ref[0]; if ( node === void 0 ) node = null;
	        return node;
	    },


	    // merge default label attrs into label attrs (or use built-in default label attrs if neither is provided)
	    // keep `undefined` or `null` because `{}` means something else
	    _mergeLabelAttrs: function(hasCustomMarkup, labelAttrs, defaultLabelAttrs, builtinDefaultLabelAttrs) {

	        if (labelAttrs === null) { return null; }
	        if (labelAttrs === undefined) {

	            if (defaultLabelAttrs === null) { return null; }
	            if (defaultLabelAttrs === undefined) {

	                if (hasCustomMarkup) { return undefined; }
	                return builtinDefaultLabelAttrs;
	            }

	            if (hasCustomMarkup) { return defaultLabelAttrs; }
	            return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs);
	        }

	        if (hasCustomMarkup) { return merge({}, defaultLabelAttrs, labelAttrs); }
	        return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs, labelAttrs);
	    },

	    // merge default label size into label size (no built-in default)
	    // keep `undefined` or `null` because `{}` means something else
	    _mergeLabelSize: function(labelSize, defaultLabelSize) {

	        if (labelSize === null) { return null; }
	        if (labelSize === undefined) {

	            if (defaultLabelSize === null) { return null; }
	            if (defaultLabelSize === undefined) { return undefined; }

	            return defaultLabelSize;
	        }

	        return merge({}, defaultLabelSize, labelSize);
	    },

	    updateLabels: function() {

	        if (!this._V.labels) { return this; }

	        var model = this.model;
	        var labels = model.get('labels') || [];
	        var canLabelMove = this.can('labelMove');

	        var builtinDefaultLabel = model._builtins.defaultLabel;
	        var builtinDefaultLabelAttrs = builtinDefaultLabel.attrs;

	        var defaultLabel = model._getDefaultLabel();
	        var defaultLabelMarkup = defaultLabel.markup;
	        var defaultLabelAttrs = defaultLabel.attrs;
	        var defaultLabelSize = defaultLabel.size;

	        for (var i = 0, n = labels.length; i < n; i++) {

	            var labelNode = this._labelCache[i];
	            labelNode.setAttribute('cursor', (canLabelMove ? 'move' : 'default'));

	            var selectors = this._labelSelectors[i];

	            var label = labels[i];
	            var labelMarkup = label.markup;
	            var labelAttrs = label.attrs;
	            var labelSize = label.size;

	            var attrs = this._mergeLabelAttrs(
	                (labelMarkup || defaultLabelMarkup),
	                labelAttrs,
	                defaultLabelAttrs,
	                builtinDefaultLabelAttrs
	            );

	            var size = this._mergeLabelSize(
	                labelSize,
	                defaultLabelSize
	            );

	            this.updateDOMSubtreeAttributes(labelNode, attrs, {
	                rootBBox: new Rect(size),
	                selectors: selectors
	            });
	        }

	        return this;
	    },

	    renderTools: function() {

	        if (!this._V.linkTools) { return this; }

	        // Tools are a group of clickable elements that manipulate the whole link.
	        // A good example of this is the remove tool that removes the whole link.
	        // Tools appear after hovering the link close to the `source` element/point of the link
	        // but are offset a bit so that they don't cover the `marker-arrowhead`.

	        var $tools = $(this._V.linkTools.node).empty();
	        var toolTemplate = template(this.model.get('toolMarkup') || this.model.toolMarkup);
	        var tool = V(toolTemplate());

	        $tools.append(tool.node);

	        // Cache the tool node so that the `updateToolsPosition()` can update the tool position quickly.
	        this._toolCache = tool;

	        // If `doubleLinkTools` is enabled, we render copy of the tools on the other side of the
	        // link as well but only if the link is longer than `longLinkLength`.
	        if (this.options.doubleLinkTools) {

	            var tool2;
	            if (this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup) {
	                toolTemplate = template(this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup);
	                tool2 = V(toolTemplate());
	            } else {
	                tool2 = tool.clone();
	            }

	            $tools.append(tool2.node);
	            this._tool2Cache = tool2;
	        }

	        return this;
	    },

	    renderVertexMarkers: function() {

	        if (!this._V.markerVertices) { return this; }

	        var $markerVertices = $(this._V.markerVertices.node).empty();

	        // A special markup can be given in the `properties.vertexMarkup` property. This might be handy
	        // if default styling (elements) are not desired. This makes it possible to use any
	        // SVG elements for .marker-vertex and .marker-vertex-remove tools.
	        var markupTemplate = template(this.model.get('vertexMarkup') || this.model.vertexMarkup);

	        this.model.vertices().forEach(function(vertex, idx) {
	            $markerVertices.append(V(markupTemplate(assign({ idx: idx }, vertex))).node);
	        });

	        return this;
	    },

	    renderArrowheadMarkers: function() {

	        // Custom markups might not have arrowhead markers. Therefore, jump of this function immediately if that's the case.
	        if (!this._V.markerArrowheads) { return this; }

	        var $markerArrowheads = $(this._V.markerArrowheads.node);

	        $markerArrowheads.empty();

	        // A special markup can be given in the `properties.vertexMarkup` property. This might be handy
	        // if default styling (elements) are not desired. This makes it possible to use any
	        // SVG elements for .marker-vertex and .marker-vertex-remove tools.
	        var markupTemplate = template(this.model.get('arrowheadMarkup') || this.model.arrowheadMarkup);

	        this._V.sourceArrowhead = V(markupTemplate({ end: 'source' }));
	        this._V.targetArrowhead = V(markupTemplate({ end: 'target' }));

	        $markerArrowheads.append(this._V.sourceArrowhead.node, this._V.targetArrowhead.node);

	        return this;
	    },

	    // remove vertices that lie on (or nearly on) straight lines within the link
	    // return the number of removed points
	    removeRedundantLinearVertices: function(opt) {

	        var SIMPLIFY_THRESHOLD = 0.001;

	        var link = this.model;
	        var vertices = link.vertices();
	        var routePoints = [this.sourceAnchor ].concat( vertices, [this.targetAnchor]);
	        var numRoutePoints = routePoints.length;

	        // put routePoints into a polyline and try to simplify
	        var polyline = new Polyline(routePoints);
	        polyline.simplify({ threshold: SIMPLIFY_THRESHOLD });
	        var polylinePoints = polyline.points.map(function (point) { return (point.toJSON()); }); // JSON of points after simplification
	        var numPolylinePoints = polylinePoints.length; // number of points after simplification

	        // shortcut if simplification did not remove any redundant vertices:
	        if (numRoutePoints === numPolylinePoints) { return 0; }

	        // else: set simplified polyline points as link vertices
	        // remove first and last polyline points again (= source/target anchors)
	        link.vertices(polylinePoints.slice(1, numPolylinePoints - 1), opt);
	        return (numRoutePoints - numPolylinePoints);
	    },

	    updateDefaultConnectionPath: function() {

	        var cache = this._V;

	        if (cache.connection) {
	            cache.connection.attr('d', this.getSerializedConnection());
	        }

	        if (cache.connectionWrap) {
	            cache.connectionWrap.attr('d', this.getSerializedConnection());
	        }

	        if (cache.markerSource && cache.markerTarget) {
	            this._translateAndAutoOrientArrows(cache.markerSource, cache.markerTarget);
	        }
	    },

	    getEndView: function(type) {
	        switch (type) {
	            case 'source':
	                return this.sourceView || null;
	            case 'target':
	                return this.targetView || null;
	            default:
	                throw new Error('dia.LinkView: type parameter required.');
	        }
	    },

	    getEndAnchor: function(type) {
	        switch (type) {
	            case 'source':
	                return new Point(this.sourceAnchor);
	            case 'target':
	                return new Point(this.targetAnchor);
	            default:
	                throw new Error('dia.LinkView: type parameter required.');
	        }
	    },

	    getEndConnectionPoint: function(type) {
	        switch (type) {
	            case 'source':
	                return new Point(this.sourcePoint);
	            case 'target':
	                return new Point(this.targetPoint);
	            default:
	                throw new Error('dia.LinkView: type parameter required.');
	        }
	    },

	    getEndMagnet: function(type) {
	        switch (type) {
	            case 'source':
	                var sourceView = this.sourceView;
	                if (!sourceView) { break; }
	                return this.sourceMagnet || sourceView.el;
	            case 'target':
	                var targetView = this.targetView;
	                if (!targetView) { break; }
	                return this.targetMagnet || targetView.el;
	            default:
	                throw new Error('dia.LinkView: type parameter required.');
	        }
	        return null;
	    },


	    // Updating.
	    // ---------

	    update: function() {
	        this.updateRoute();
	        this.updatePath();
	        this.updateDOM();
	        return this;
	    },

	    translate: function(tx, ty) {
	        if ( tx === void 0 ) tx = 0;
	        if ( ty === void 0 ) ty = 0;

	        var ref = this;
	        var route = ref.route;
	        var path = ref.path;
	        if (!route || !path) { return; }
	        // translate the route
	        var polyline = new Polyline(route);
	        polyline.translate(tx, ty);
	        this.route = polyline.points;
	        // translate source and target connection and marker points.
	        this._translateConnectionPoints(tx, ty);
	        // translate the geometry path
	        path.translate(tx, ty);
	        this.updateDOM();
	    },

	    updateDOM: function updateDOM() {
	        var ref = this;
	        var el = ref.el;
	        var model = ref.model;
	        var selectors = ref.selectors;
	        this.cleanNodesCache();
	        // update SVG attributes defined by 'attrs/'.
	        this.updateDOMSubtreeAttributes(el, model.attr(), { selectors: selectors });
	        // legacy link path update
	        this.updateDefaultConnectionPath();
	        // update the label position etc.
	        this.updateLabelPositions();
	        this.updateToolsPosition();
	        this.updateArrowheadMarkers();
	        // *Deprecated*
	        // Local perpendicular flag (as opposed to one defined on paper).
	        // Could be enabled inside a connector/router. It's valid only
	        // during the update execution.
	        this.options.perpendicular = null;
	    },

	    updateRoute: function() {
	        var ref = this;
	        var model = ref.model;
	        var vertices = model.vertices();
	        // 1. Find Anchors
	        var anchors = this.findAnchors(vertices);
	        var sourceAnchor = this.sourceAnchor = anchors.source;
	        var targetAnchor = this.targetAnchor = anchors.target;
	        // 2. Find Route
	        var route = this.findRoute(vertices);
	        this.route = route;
	        // 3. Find Connection Points
	        var connectionPoints = this.findConnectionPoints(route, sourceAnchor, targetAnchor);
	        this.sourcePoint = connectionPoints.source;
	        this.targetPoint = connectionPoints.target;
	    },

	    updatePath: function() {
	        var ref = this;
	        var route = ref.route;
	        var sourcePoint = ref.sourcePoint;
	        var targetPoint = ref.targetPoint;
	        // 3b. Find Marker Connection Point - Backwards Compatibility
	        var markerPoints = this.findMarkerPoints(route, sourcePoint, targetPoint);
	        // 4. Find Connection
	        var path = this.findPath(route, markerPoints.source || sourcePoint, markerPoints.target || targetPoint);
	        this.path = path;
	    },

	    findMarkerPoints: function(route, sourcePoint, targetPoint) {

	        var firstWaypoint = route[0];
	        var lastWaypoint = route[route.length - 1];

	        // Move the source point by the width of the marker taking into account
	        // its scale around x-axis. Note that scale is the only transform that
	        // makes sense to be set in `.marker-source` attributes object
	        // as all other transforms (translate/rotate) will be replaced
	        // by the `translateAndAutoOrient()` function.
	        var cache = this._markerCache;
	        // cache source and target points
	        var sourceMarkerPoint, targetMarkerPoint;

	        if (this._V.markerSource) {

	            cache.sourceBBox = cache.sourceBBox || this._V.markerSource.getBBox();
	            sourceMarkerPoint = Point(sourcePoint).move(
	                firstWaypoint || targetPoint,
	                cache.sourceBBox.width * this._V.markerSource.scale().sx * -1
	            ).round();
	        }

	        if (this._V.markerTarget) {

	            cache.targetBBox = cache.targetBBox || this._V.markerTarget.getBBox();
	            targetMarkerPoint = Point(targetPoint).move(
	                lastWaypoint || sourcePoint,
	                cache.targetBBox.width * this._V.markerTarget.scale().sx * -1
	            ).round();
	        }

	        // if there was no markup for the marker, use the connection point.
	        cache.sourcePoint = sourceMarkerPoint || sourcePoint.clone();
	        cache.targetPoint = targetMarkerPoint || targetPoint.clone();

	        return {
	            source: sourceMarkerPoint,
	            target: targetMarkerPoint
	        };
	    },

	    findAnchorsOrdered: function(firstEndType, firstRef, secondEndType, secondRef) {

	        var firstAnchor, secondAnchor;
	        var firstAnchorRef, secondAnchorRef;
	        var model = this.model;
	        var firstDef = model.get(firstEndType);
	        var secondDef = model.get(secondEndType);
	        var firstView = this.getEndView(firstEndType);
	        var secondView = this.getEndView(secondEndType);
	        var firstMagnet = this.getEndMagnet(firstEndType);
	        var secondMagnet = this.getEndMagnet(secondEndType);

	        // Anchor first
	        if (firstView) {
	            if (firstRef) {
	                firstAnchorRef = new Point(firstRef);
	            } else if (secondView) {
	                firstAnchorRef = secondMagnet;
	            } else {
	                firstAnchorRef = new Point(secondDef);
	            }
	            firstAnchor = this.getAnchor(firstDef.anchor, firstView, firstMagnet, firstAnchorRef, firstEndType);
	        } else {
	            firstAnchor = new Point(firstDef);
	        }

	        // Anchor second
	        if (secondView) {
	            secondAnchorRef = new Point(secondRef || firstAnchor);
	            secondAnchor = this.getAnchor(secondDef.anchor, secondView, secondMagnet, secondAnchorRef, secondEndType);
	        } else {
	            secondAnchor = new Point(secondDef);
	        }

	        var res = {};
	        res[firstEndType] = firstAnchor;
	        res[secondEndType] = secondAnchor;
	        return res;
	    },

	    findAnchors: function(vertices) {

	        var model = this.model;
	        var firstVertex = vertices[0];
	        var lastVertex = vertices[vertices.length - 1];

	        if (model.target().priority && !model.source().priority) {
	            // Reversed order
	            return this.findAnchorsOrdered('target', lastVertex, 'source', firstVertex);
	        }

	        // Usual order
	        return this.findAnchorsOrdered('source', firstVertex, 'target', lastVertex);
	    },

	    findConnectionPoints: function(route, sourceAnchor, targetAnchor) {

	        var firstWaypoint = route[0];
	        var lastWaypoint = route[route.length - 1];
	        var model = this.model;
	        var sourceDef = model.get('source');
	        var targetDef = model.get('target');
	        var sourceView = this.sourceView;
	        var targetView = this.targetView;
	        var paperOptions = this.paper.options;
	        var sourceMagnet, targetMagnet;

	        // Connection Point Source
	        var sourcePoint;
	        if (sourceView && !sourceView.isNodeConnection(this.sourceMagnet)) {
	            sourceMagnet = (this.sourceMagnet || sourceView.el);
	            var sourceConnectionPointDef = sourceDef.connectionPoint || paperOptions.defaultConnectionPoint;
	            var sourcePointRef = firstWaypoint || targetAnchor;
	            var sourceLine = new Line(sourcePointRef, sourceAnchor);
	            sourcePoint = this.getConnectionPoint(
	                sourceConnectionPointDef,
	                sourceView,
	                sourceMagnet,
	                sourceLine,
	                'source'
	            );
	        } else {
	            sourcePoint = sourceAnchor;
	        }
	        // Connection Point Target
	        var targetPoint;
	        if (targetView && !targetView.isNodeConnection(this.targetMagnet)) {
	            targetMagnet = (this.targetMagnet || targetView.el);
	            var targetConnectionPointDef = targetDef.connectionPoint || paperOptions.defaultConnectionPoint;
	            var targetPointRef = lastWaypoint || sourceAnchor;
	            var targetLine = new Line(targetPointRef, targetAnchor);
	            targetPoint = this.getConnectionPoint(
	                targetConnectionPointDef,
	                targetView,
	                targetMagnet,
	                targetLine,
	                'target'
	            );
	        } else {
	            targetPoint = targetAnchor;
	        }

	        return {
	            source: sourcePoint,
	            target: targetPoint
	        };
	    },

	    getAnchor: function(anchorDef, cellView, magnet, ref, endType) {

	        var isConnection = cellView.isNodeConnection(magnet);
	        var paperOptions = this.paper.options;
	        if (!anchorDef) {
	            if (isConnection) {
	                anchorDef = paperOptions.defaultLinkAnchor;
	            } else {
	                if (paperOptions.perpendicularLinks || this.options.perpendicular) {
	                    // Backwards compatibility
	                    // If `perpendicularLinks` flag is set on the paper and there are vertices
	                    // on the link, then try to find a connection point that makes the link perpendicular
	                    // even though the link won't point to the center of the targeted object.
	                    anchorDef = { name: 'perpendicular' };
	                } else {
	                    anchorDef = paperOptions.defaultAnchor;
	                }
	            }
	        }

	        if (!anchorDef) { throw new Error('Anchor required.'); }
	        var anchorFn;
	        if (typeof anchorDef === 'function') {
	            anchorFn = anchorDef;
	        } else {
	            var anchorName = anchorDef.name;
	            var anchorNamespace = isConnection ? 'linkAnchorNamespace' : 'anchorNamespace';
	            anchorFn = paperOptions[anchorNamespace][anchorName];
	            if (typeof anchorFn !== 'function') { throw new Error('Unknown anchor: ' + anchorName); }
	        }
	        var anchor = anchorFn.call(
	            this,
	            cellView,
	            magnet,
	            ref,
	            anchorDef.args || {},
	            endType,
	            this
	        );
	        if (!anchor) { return new Point(); }
	        return anchor.round(this.decimalsRounding);
	    },


	    getConnectionPoint: function(connectionPointDef, view, magnet, line, endType) {

	        var connectionPoint;
	        var anchor = line.end;
	        var paperOptions = this.paper.options;

	        // Backwards compatibility
	        if (typeof paperOptions.linkConnectionPoint === 'function') {
	            var linkConnectionMagnet = (magnet === view.el) ? undefined : magnet;
	            connectionPoint = paperOptions.linkConnectionPoint(this, view, linkConnectionMagnet, line.start, endType);
	            if (connectionPoint) { return connectionPoint; }
	        }

	        if (!connectionPointDef) { return anchor; }
	        var connectionPointFn;
	        if (typeof connectionPointDef === 'function') {
	            connectionPointFn = connectionPointDef;
	        } else {
	            var connectionPointName = connectionPointDef.name;
	            connectionPointFn = paperOptions.connectionPointNamespace[connectionPointName];
	            if (typeof connectionPointFn !== 'function') { throw new Error('Unknown connection point: ' + connectionPointName); }
	        }
	        connectionPoint = connectionPointFn.call(this, line, view, magnet, connectionPointDef.args || {}, endType, this);
	        if (!connectionPoint) { return anchor; }
	        return connectionPoint.round(this.decimalsRounding);
	    },

	    _translateConnectionPoints: function(tx, ty) {

	        var cache = this._markerCache;

	        cache.sourcePoint.offset(tx, ty);
	        cache.targetPoint.offset(tx, ty);
	        this.sourcePoint.offset(tx, ty);
	        this.targetPoint.offset(tx, ty);
	        this.sourceAnchor.offset(tx, ty);
	        this.targetAnchor.offset(tx, ty);
	    },

	    // combine default label position with built-in default label position
	    _getDefaultLabelPositionProperty: function() {

	        var model = this.model;

	        var builtinDefaultLabel = model._builtins.defaultLabel;
	        var builtinDefaultLabelPosition = builtinDefaultLabel.position;

	        var defaultLabel = model._getDefaultLabel();
	        var defaultLabelPosition = this._normalizeLabelPosition(defaultLabel.position);

	        return merge({}, builtinDefaultLabelPosition, defaultLabelPosition);
	    },

	    // if label position is a number, normalize it to a position object
	    // this makes sure that label positions can be merged properly
	    _normalizeLabelPosition: function(labelPosition) {

	        if (typeof labelPosition === 'number') { return { distance: labelPosition, offset: null, angle: 0, args: null }; }
	        return labelPosition;
	    },

	    // expects normalized position properties
	    // e.g. `this._normalizeLabelPosition(labelPosition)` and `this._getDefaultLabelPositionProperty()`
	    _mergeLabelPositionProperty: function(normalizedLabelPosition, normalizedDefaultLabelPosition) {

	        if (normalizedLabelPosition === null) { return null; }
	        if (normalizedLabelPosition === undefined) {

	            if (normalizedDefaultLabelPosition === null) { return null; }
	            return normalizedDefaultLabelPosition;
	        }

	        return merge({}, normalizedDefaultLabelPosition, normalizedLabelPosition);
	    },

	    updateLabelPositions: function() {

	        if (!this._V.labels) { return this; }

	        var path = this.path;
	        if (!path) { return this; }

	        // This method assumes all the label nodes are stored in the `this._labelCache` hash table
	        // by their indices in the `this.get('labels')` array. This is done in the `renderLabels()` method.

	        var model = this.model;
	        var labels = model.get('labels') || [];
	        if (!labels.length) { return this; }

	        var defaultLabelPosition = this._getDefaultLabelPositionProperty();

	        for (var idx = 0, n = labels.length; idx < n; idx++) {
	            var labelNode = this._labelCache[idx];
	            if (!labelNode) { continue; }
	            var label = labels[idx];
	            var labelPosition = this._normalizeLabelPosition(label.position);
	            var position = this._mergeLabelPositionProperty(labelPosition, defaultLabelPosition);
	            var transformationMatrix = this._getLabelTransformationMatrix(position);
	            labelNode.setAttribute('transform', V.matrixToTransformString(transformationMatrix));
	            this._cleanLabelMatrices(idx);
	        }

	        return this;
	    },

	    _cleanLabelMatrices: function(index) {
	        // Clean magnetMatrix for all nodes of the label.
	        // Cached BoundingRect does not need to updated when the position changes
	        // TODO: this doesn't work for labels with XML String markups.
	        var ref = this;
	        var metrics = ref.metrics;
	        var _labelSelectors = ref._labelSelectors;
	        var selectors = _labelSelectors[index];
	        if (!selectors) { return; }
	        for (var selector in selectors) {
	            var ref$1 = selectors[selector];
	            var id = ref$1.id;
	            if (id && (id in metrics)) { delete metrics[id].magnetMatrix; }
	        }
	    },

	    updateToolsPosition: function() {

	        if (!this._V.linkTools) { return this; }

	        // Move the tools a bit to the target position but don't cover the `sourceArrowhead` marker.
	        // Note that the offset is hardcoded here. The offset should be always
	        // more than the `this.$('.marker-arrowhead[end="source"]')[0].bbox().width` but looking
	        // this up all the time would be slow.

	        var scale = '';
	        var offset = this.options.linkToolsOffset;
	        var connectionLength = this.getConnectionLength();

	        // Firefox returns connectionLength=NaN in odd cases (for bezier curves).
	        // In that case we won't update tools position at all.
	        if (!Number.isNaN(connectionLength)) {

	            // If the link is too short, make the tools half the size and the offset twice as low.
	            if (connectionLength < this.options.shortLinkLength) {
	                scale = 'scale(.5)';
	                offset /= 2;
	            }

	            var toolPosition = this.getPointAtLength(offset);

	            this._toolCache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale);

	            if (this.options.doubleLinkTools && connectionLength >= this.options.longLinkLength) {

	                var doubleLinkToolsOffset = this.options.doubleLinkToolsOffset || offset;

	                toolPosition = this.getPointAtLength(connectionLength - doubleLinkToolsOffset);
	                this._tool2Cache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale);
	                this._tool2Cache.attr('visibility', 'visible');

	            } else if (this.options.doubleLinkTools) {

	                this._tool2Cache.attr('visibility', 'hidden');
	            }
	        }

	        return this;
	    },

	    updateArrowheadMarkers: function() {

	        if (!this._V.markerArrowheads) { return this; }

	        // getting bbox of an element with `display="none"` in IE9 ends up with access violation
	        if ($.css(this._V.markerArrowheads.node, 'display') === 'none') { return this; }

	        var sx = this.getConnectionLength() < this.options.shortLinkLength ? .5 : 1;
	        this._V.sourceArrowhead.scale(sx);
	        this._V.targetArrowhead.scale(sx);

	        this._translateAndAutoOrientArrows(this._V.sourceArrowhead, this._V.targetArrowhead);

	        return this;
	    },

	    updateEndProperties: function(endType) {

	        var ref = this;
	        var model = ref.model;
	        var paper = ref.paper;
	        var endViewProperty = endType + "View";
	        var endDef = model.get(endType);
	        var endId = endDef && endDef.id;

	        if (!endId) {
	            // the link end is a point ~ rect 0x0
	            this[endViewProperty] = null;
	            this.updateEndMagnet(endType);
	            return true;
	        }

	        var endModel = paper.getModelById(endId);
	        if (!endModel) { throw new Error('LinkView: invalid ' + endType + ' cell.'); }

	        var endView = endModel.findView(paper);
	        if (!endView) {
	            // A view for a model should always exist
	            return false;
	        }

	        this[endViewProperty] = endView;
	        this.updateEndMagnet(endType);
	        return true;
	    },

	    updateEndMagnet: function(endType) {

	        var endMagnetProperty = endType + "Magnet";
	        var endView = this.getEndView(endType);
	        if (endView) {
	            var connectedMagnet = endView.getMagnetFromLinkEnd(this.model.get(endType));
	            if (connectedMagnet === endView.el) { connectedMagnet = null; }
	            this[endMagnetProperty] = connectedMagnet;
	        } else {
	            this[endMagnetProperty] = null;
	        }
	    },

	    _translateAndAutoOrientArrows: function(sourceArrow, targetArrow) {

	        // Make the markers "point" to their sticky points being auto-oriented towards
	        // `targetPosition`/`sourcePosition`. And do so only if there is a markup for them.
	        var route = toArray(this.route);
	        if (sourceArrow) {
	            sourceArrow.translateAndAutoOrient(
	                this.sourcePoint,
	                route[0] || this.targetPoint,
	                this.paper.cells
	            );
	        }

	        if (targetArrow) {
	            targetArrow.translateAndAutoOrient(
	                this.targetPoint,
	                route[route.length - 1] || this.sourcePoint,
	                this.paper.cells
	            );
	        }
	    },

	    _getLabelPositionProperty: function(idx) {

	        return (this.model.label(idx).position || {});
	    },

	    _getLabelPositionAngle: function(idx) {

	        var labelPosition = this._getLabelPositionProperty(idx);
	        return (labelPosition.angle || 0);
	    },

	    _getLabelPositionArgs: function(idx) {

	        var labelPosition = this._getLabelPositionProperty(idx);
	        return labelPosition.args;
	    },

	    _getDefaultLabelPositionArgs: function() {

	        var defaultLabel = this.model._getDefaultLabel();
	        var defaultLabelPosition = defaultLabel.position || {};
	        return defaultLabelPosition.args;
	    },

	    // merge default label position args into label position args
	    // keep `undefined` or `null` because `{}` means something else
	    _mergeLabelPositionArgs: function(labelPositionArgs, defaultLabelPositionArgs) {

	        if (labelPositionArgs === null) { return null; }
	        if (labelPositionArgs === undefined) {

	            if (defaultLabelPositionArgs === null) { return null; }
	            return defaultLabelPositionArgs;
	        }

	        return merge({}, defaultLabelPositionArgs, labelPositionArgs);
	    },

	    // Add default label at given position at end of `labels` array.
	    // Four signatures:
	    // - obj, obj = point, opt
	    // - obj, num, obj = point, angle, opt
	    // - num, num, obj = x, y, opt
	    // - num, num, num, obj = x, y, angle, opt
	    // Assigns relative coordinates by default:
	    // `opt.absoluteDistance` forces absolute coordinates.
	    // `opt.reverseDistance` forces reverse absolute coordinates (if absoluteDistance = true).
	    // `opt.absoluteOffset` forces absolute coordinates for offset.
	    // Additional args:
	    // `opt.keepGradient` auto-adjusts the angle of the label to match path gradient at position.
	    // `opt.ensureLegibility` rotates labels so they are never upside-down.
	    addLabel: function(p1, p2, p3, p4) {

	        // normalize data from the four possible signatures
	        var localX;
	        var localY;
	        var localAngle = 0;
	        var localOpt;
	        if (typeof p1 !== 'number') {
	            // {x, y} object provided as first parameter
	            localX = p1.x;
	            localY = p1.y;
	            if (typeof p2 === 'number') {
	                // angle and opt provided as second and third parameters
	                localAngle = p2;
	                localOpt = p3;
	            } else {
	                // opt provided as second parameter
	                localOpt = p2;
	            }
	        } else {
	            // x and y provided as first and second parameters
	            localX = p1;
	            localY = p2;
	            if (typeof p3 === 'number') {
	                // angle and opt provided as third and fourth parameters
	                localAngle = p3;
	                localOpt = p4;
	            } else {
	                // opt provided as third parameter
	                localOpt = p3;
	            }
	        }

	        // merge label position arguments
	        var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs();
	        var labelPositionArgs = localOpt;
	        var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs);

	        // append label to labels array
	        var label = { position: this.getLabelPosition(localX, localY, localAngle, positionArgs) };
	        var idx = -1;
	        this.model.insertLabel(idx, label, localOpt);
	        return idx;
	    },

	    // Add a new vertex at calculated index to the `vertices` array.
	    addVertex: function(x, y, opt) {

	        // accept input in form `{ x, y }, opt` or `x, y, opt`
	        var isPointProvided = (typeof x !== 'number');
	        var localX = isPointProvided ? x.x : x;
	        var localY = isPointProvided ? x.y : y;
	        var localOpt = isPointProvided ? y : opt;

	        var vertex = { x: localX, y: localY };
	        var idx = this.getVertexIndex(localX, localY);
	        this.model.insertVertex(idx, vertex, localOpt);
	        return idx;
	    },

	    // Send a token (an SVG element, usually a circle) along the connection path.
	    // Example: `link.findView(paper).sendToken(V('circle', { r: 7, fill: 'green' }).node)`
	    // `opt.duration` is optional and is a time in milliseconds that the token travels from the source to the target of the link. Default is `1000`.
	    // `opt.directon` is optional and it determines whether the token goes from source to target or other way round (`reverse`)
	    // `opt.connection` is an optional selector to the connection path.
	    // `callback` is optional and is a function to be called once the token reaches the target.
	    sendToken: function(token, opt, callback) {

	        function onAnimationEnd(vToken, callback) {
	            return function() {
	                vToken.remove();
	                if (typeof callback === 'function') {
	                    callback();
	                }
	            };
	        }

	        var duration, isReversed, selector;
	        if (isObject$1(opt)) {
	            duration = opt.duration;
	            isReversed = (opt.direction === 'reverse');
	            selector = opt.connection;
	        } else {
	            // Backwards compatibility
	            duration = opt;
	            isReversed = false;
	            selector = null;
	        }

	        duration = duration || 1000;

	        var animationAttributes = {
	            dur: duration + 'ms',
	            repeatCount: 1,
	            calcMode: 'linear',
	            fill: 'freeze'
	        };

	        if (isReversed) {
	            animationAttributes.keyPoints = '1;0';
	            animationAttributes.keyTimes = '0;1';
	        }

	        var vToken = V(token);
	        var connection;
	        if (typeof selector === 'string') {
	            // Use custom connection path.
	            connection = this.findBySelector(selector, this.el, this.selectors)[0];
	        } else {
	            // Select connection path automatically.
	            var cache = this._V;
	            connection = (cache.connection) ? cache.connection.node : this.el.querySelector('path');
	        }

	        if (!(connection instanceof SVGPathElement)) {
	            throw new Error('dia.LinkView: token animation requires a valid connection path.');
	        }

	        vToken
	            .appendTo(this.paper.cells)
	            .animateAlongPath(animationAttributes, connection);

	        setTimeout(onAnimationEnd(vToken, callback), duration);
	    },

	    findRoute: function(vertices) {

	        vertices || (vertices = []);

	        var namespace = this.paper.options.routerNamespace || routers;
	        var router = this.model.router();
	        var defaultRouter = this.paper.options.defaultRouter;

	        if (!router) {
	            if (defaultRouter) { router = defaultRouter; }
	            else { return vertices.map(Point); } // no router specified
	        }

	        var routerFn = isFunction(router) ? router : namespace[router.name];
	        if (!isFunction(routerFn)) {
	            throw new Error('dia.LinkView: unknown router: "' + router.name + '".');
	        }

	        var args = router.args || {};

	        var route = routerFn.call(
	            this, // context
	            vertices, // vertices
	            args, // options
	            this // linkView
	        );

	        if (!route) { return vertices.map(Point); }
	        return route;
	    },

	    // Return the `d` attribute value of the `<path>` element representing the link
	    // between `source` and `target`.
	    findPath: function(route, sourcePoint, targetPoint) {

	        var namespace = this.paper.options.connectorNamespace || connectors;
	        var connector = this.model.connector();
	        var defaultConnector = this.paper.options.defaultConnector;

	        if (!connector) {
	            connector = defaultConnector || {};
	        }

	        var connectorFn = isFunction(connector) ? connector : namespace[connector.name];
	        if (!isFunction(connectorFn)) {
	            throw new Error('dia.LinkView: unknown connector: "' + connector.name + '".');
	        }

	        var args = clone(connector.args || {});
	        args.raw = true; // Request raw g.Path as the result.

	        var path = connectorFn.call(
	            this, // context
	            sourcePoint, // start point
	            targetPoint, // end point
	            route, // vertices
	            args, // options
	            this // linkView
	        );

	        if (typeof path === 'string') {
	            // Backwards compatibility for connectors not supporting `raw` option.
	            path = new Path(V.normalizePathData(path));
	        }

	        return path;
	    },

	    // Public API.
	    // -----------

	    getConnection: function() {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.clone();
	    },

	    getSerializedConnection: function() {

	        var path = this.path;
	        if (!path) { return null; }

	        var metrics = this.metrics;
	        if (metrics.hasOwnProperty('data')) { return metrics.data; }
	        var data = path.serialize();
	        metrics.data = data;
	        return data;
	    },

	    getConnectionSubdivisions: function() {

	        var path = this.path;
	        if (!path) { return null; }

	        var metrics = this.metrics;
	        if (metrics.hasOwnProperty('segmentSubdivisions')) { return metrics.segmentSubdivisions; }
	        var subdivisions = path.getSegmentSubdivisions();
	        metrics.segmentSubdivisions = subdivisions;
	        return subdivisions;
	    },

	    getConnectionLength: function() {

	        var path = this.path;
	        if (!path) { return 0; }

	        var metrics = this.metrics;
	        if (metrics.hasOwnProperty('length')) { return metrics.length; }
	        var length = path.length({ segmentSubdivisions: this.getConnectionSubdivisions() });
	        metrics.length = length;
	        return length;
	    },

	    getPointAtLength: function(length) {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.pointAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    getPointAtRatio: function(ratio) {

	        var path = this.path;
	        if (!path) { return null; }
	        if (isPercentage(ratio)) { ratio = parseFloat(ratio) / 100; }
	        return path.pointAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    getTangentAtLength: function(length) {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.tangentAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    getTangentAtRatio: function(ratio) {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.tangentAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    getClosestPoint: function(point) {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.closestPoint(point, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    getClosestPointLength: function(point) {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.closestPointLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    getClosestPointRatio: function(point) {

	        var path = this.path;
	        if (!path) { return null; }

	        return path.closestPointNormalizedLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() });
	    },

	    // Get label position object based on two provided coordinates, x and y.
	    // (Used behind the scenes when user moves labels around.)
	    // Two signatures:
	    // - num, num, obj = x, y, options
	    // - num, num, num, obj = x, y, angle, options
	    // Accepts distance/offset options = `absoluteDistance: boolean`, `reverseDistance: boolean`, `absoluteOffset: boolean`
	    // - `absoluteOffset` is necessary in order to move beyond connection endpoints
	    // Additional options = `keepGradient: boolean`, `ensureLegibility: boolean`
	    getLabelPosition: function(x, y, p3, p4) {

	        var position = {};

	        // normalize data from the two possible signatures
	        var localAngle = 0;
	        var localOpt;
	        if (typeof p3 === 'number') {
	            // angle and opt provided as third and fourth argument
	            localAngle = p3;
	            localOpt = p4;
	        } else {
	            // opt provided as third argument
	            localOpt = p3;
	        }

	        // save localOpt as `args` of the position object that is passed along
	        if (localOpt) { position.args = localOpt; }

	        // identify distance/offset settings
	        var isDistanceRelative = !(localOpt && localOpt.absoluteDistance); // relative by default
	        var isDistanceAbsoluteReverse = (localOpt && localOpt.absoluteDistance && localOpt.reverseDistance); // non-reverse by default
	        var isOffsetAbsolute = localOpt && localOpt.absoluteOffset; // offset is non-absolute by default

	        // find closest point t
	        var path = this.path;
	        var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() };
	        var labelPoint = new Point(x, y);
	        var t = path.closestPointT(labelPoint, pathOpt);

	        // DISTANCE:
	        var labelDistance = path.lengthAtT(t, pathOpt);
	        if (isDistanceRelative) { labelDistance = (labelDistance / this.getConnectionLength()) || 0; } // fix to prevent NaN for 0 length
	        if (isDistanceAbsoluteReverse) { labelDistance = (-1 * (this.getConnectionLength() - labelDistance)) || 1; } // fix for end point (-0 => 1)
	        position.distance = labelDistance;

	        // OFFSET:
	        // use absolute offset if:
	        // - opt.absoluteOffset is true,
	        // - opt.absoluteOffset is not true but there is no tangent
	        var tangent;
	        if (!isOffsetAbsolute) { tangent = path.tangentAtT(t); }
	        var labelOffset;
	        if (tangent) {
	            labelOffset = tangent.pointOffset(labelPoint);
	        } else {
	            var closestPoint = path.pointAtT(t);
	            var labelOffsetDiff = labelPoint.difference(closestPoint);
	            labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y };
	        }
	        position.offset = labelOffset;

	        // ANGLE:
	        position.angle = localAngle;

	        return position;
	    },

	    _getLabelTransformationMatrix: function(labelPosition) {

	        var labelDistance;
	        var labelAngle = 0;
	        var args = {};
	        if (typeof labelPosition === 'number') {
	            labelDistance = labelPosition;
	        } else if (typeof labelPosition.distance === 'number') {
	            args = labelPosition.args || {};
	            labelDistance = labelPosition.distance;
	            labelAngle = labelPosition.angle || 0;
	        } else {
	            throw new Error('dia.LinkView: invalid label position distance.');
	        }

	        var isDistanceRelative = ((labelDistance > 0) && (labelDistance <= 1));

	        var labelOffset = 0;
	        var labelOffsetCoordinates = { x: 0, y: 0 };
	        if (labelPosition.offset) {
	            var positionOffset = labelPosition.offset;
	            if (typeof positionOffset === 'number') { labelOffset = positionOffset; }
	            if (positionOffset.x) { labelOffsetCoordinates.x = positionOffset.x; }
	            if (positionOffset.y) { labelOffsetCoordinates.y = positionOffset.y; }
	        }

	        var isOffsetAbsolute = ((labelOffsetCoordinates.x !== 0) || (labelOffsetCoordinates.y !== 0) || labelOffset === 0);

	        var isKeepGradient = args.keepGradient;
	        var isEnsureLegibility = args.ensureLegibility;

	        var path = this.path;
	        var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() };

	        var distance = isDistanceRelative ? (labelDistance * this.getConnectionLength()) : labelDistance;
	        var tangent = path.tangentAtLength(distance, pathOpt);

	        var translation;
	        var angle = labelAngle;
	        if (tangent) {
	            if (isOffsetAbsolute) {
	                translation = tangent.start.clone();
	                translation.offset(labelOffsetCoordinates);
	            } else {
	                var normal = tangent.clone();
	                normal.rotate(tangent.start, -90);
	                normal.setLength(labelOffset);
	                translation = normal.end;
	            }

	            if (isKeepGradient) {
	                angle = (tangent.angle() + labelAngle);
	                if (isEnsureLegibility) {
	                    angle = normalizeAngle(((angle + 90) % 180) - 90);
	                }
	            }

	        } else {
	            // fallback - the connection has zero length
	            translation = path.start.clone();
	            if (isOffsetAbsolute) { translation.offset(labelOffsetCoordinates); }
	        }

	        return V.createSVGMatrix()
	            .translate(translation.x, translation.y)
	            .rotate(angle);
	    },

	    getLabelCoordinates: function(labelPosition) {

	        var transformationMatrix = this._getLabelTransformationMatrix(labelPosition);
	        return new Point(transformationMatrix.e, transformationMatrix.f);
	    },

	    getVertexIndex: function(x, y) {

	        var model = this.model;
	        var vertices = model.vertices();

	        var vertexLength = this.getClosestPointLength(new Point(x, y));

	        var idx = 0;
	        for (var n = vertices.length; idx < n; idx++) {
	            var currentVertex = vertices[idx];
	            var currentVertexLength = this.getClosestPointLength(currentVertex);
	            if (vertexLength < currentVertexLength) { break; }
	        }

	        return idx;
	    },

	    // Interaction. The controller part.
	    // ---------------------------------

	    notifyPointerdown: function notifyPointerdown(evt, x, y) {
	        CellView.prototype.pointerdown.call(this, evt, x, y);
	        this.notify('link:pointerdown', evt, x, y);
	    },

	    notifyPointermove: function notifyPointermove(evt, x, y) {
	        CellView.prototype.pointermove.call(this, evt, x, y);
	        this.notify('link:pointermove', evt, x, y);
	    },

	    notifyPointerup: function notifyPointerup(evt, x, y) {
	        this.notify('link:pointerup', evt, x, y);
	        CellView.prototype.pointerup.call(this, evt, x, y);
	    },

	    pointerdblclick: function(evt, x, y) {

	        CellView.prototype.pointerdblclick.apply(this, arguments);
	        this.notify('link:pointerdblclick', evt, x, y);
	    },

	    pointerclick: function(evt, x, y) {

	        CellView.prototype.pointerclick.apply(this, arguments);
	        this.notify('link:pointerclick', evt, x, y);
	    },

	    contextmenu: function(evt, x, y) {

	        CellView.prototype.contextmenu.apply(this, arguments);
	        this.notify('link:contextmenu', evt, x, y);
	    },

	    pointerdown: function(evt, x, y) {

	        this.notifyPointerdown(evt, x, y);

	        // Backwards compatibility for the default markup
	        var className = evt.target.getAttribute('class');
	        switch (className) {

	            case 'marker-vertex':
	                this.dragVertexStart(evt, x, y);
	                return;

	            case 'marker-vertex-remove':
	            case 'marker-vertex-remove-area':
	                this.dragVertexRemoveStart(evt, x, y);
	                return;

	            case 'marker-arrowhead':
	                this.dragArrowheadStart(evt, x, y);
	                return;

	            case 'connection':
	            case 'connection-wrap':
	                this.dragConnectionStart(evt, x, y);
	                return;

	            case 'marker-source':
	            case 'marker-target':
	                return;
	        }

	        this.dragStart(evt, x, y);
	    },

	    pointermove: function(evt, x, y) {

	        // Backwards compatibility
	        var dragData = this._dragData;
	        if (dragData) { this.eventData(evt, dragData); }

	        var data = this.eventData(evt);
	        switch (data.action) {

	            case 'vertex-move':
	                this.dragVertex(evt, x, y);
	                break;

	            case 'label-move':
	                this.dragLabel(evt, x, y);
	                break;

	            case 'arrowhead-move':
	                this.dragArrowhead(evt, x, y);
	                break;

	            case 'move':
	                this.drag(evt, x, y);
	                break;
	        }

	        // Backwards compatibility
	        if (dragData) { assign(dragData, this.eventData(evt)); }

	        this.notifyPointermove(evt, x, y);
	    },

	    pointerup: function(evt, x, y) {

	        // Backwards compatibility
	        var dragData = this._dragData;
	        if (dragData) {
	            this.eventData(evt, dragData);
	            this._dragData = null;
	        }

	        var data = this.eventData(evt);
	        switch (data.action) {

	            case 'vertex-move':
	                this.dragVertexEnd(evt, x, y);
	                break;

	            case 'label-move':
	                this.dragLabelEnd(evt, x, y);
	                break;

	            case 'arrowhead-move':
	                this.dragArrowheadEnd(evt, x, y);
	                break;

	            case 'move':
	                this.dragEnd(evt, x, y);
	        }

	        this.notifyPointerup(evt, x, y);
	        this.checkMouseleave(evt);
	    },

	    mouseover: function(evt) {

	        CellView.prototype.mouseover.apply(this, arguments);
	        this.notify('link:mouseover', evt);
	    },

	    mouseout: function(evt) {

	        CellView.prototype.mouseout.apply(this, arguments);
	        this.notify('link:mouseout', evt);
	    },

	    mouseenter: function(evt) {

	        CellView.prototype.mouseenter.apply(this, arguments);
	        this.notify('link:mouseenter', evt);
	    },

	    mouseleave: function(evt) {

	        CellView.prototype.mouseleave.apply(this, arguments);
	        this.notify('link:mouseleave', evt);
	    },

	    mousewheel: function(evt, x, y, delta) {

	        CellView.prototype.mousewheel.apply(this, arguments);
	        this.notify('link:mousewheel', evt, x, y, delta);
	    },

	    onevent: function(evt, eventName, x, y) {

	        // Backwards compatibility
	        var linkTool = V(evt.target).findParentByClass('link-tool', this.el);
	        if (linkTool) {
	            // No further action to be executed
	            evt.stopPropagation();

	            // Allow `interactive.useLinkTools=false`
	            if (this.can('useLinkTools')) {
	                if (eventName === 'remove') {
	                    // Built-in remove event
	                    this.model.remove({ ui: true });
	                    // Do not trigger link pointerdown
	                    return;

	                } else {
	                    // link:options and other custom events inside the link tools
	                    this.notify(eventName, evt, x, y);
	                }
	            }

	            this.notifyPointerdown(evt, x, y);
	            this.paper.delegateDragEvents(this, evt.data);

	        } else {
	            CellView.prototype.onevent.apply(this, arguments);
	        }
	    },

	    onlabel: function(evt, x, y) {

	        this.notifyPointerdown(evt, x, y);

	        this.dragLabelStart(evt, x, y);

	        var stopPropagation = this.eventData(evt).stopPropagation;
	        if (stopPropagation) { evt.stopPropagation(); }
	    },

	    // Drag Start Handlers

	    dragConnectionStart: function(evt, x, y) {

	        if (!this.can('vertexAdd')) { return; }

	        // Store the index at which the new vertex has just been placed.
	        // We'll be update the very same vertex position in `pointermove()`.
	        var vertexIdx = this.addVertex({ x: x, y: y }, { ui: true });
	        this.eventData(evt, {
	            action: 'vertex-move',
	            vertexIdx: vertexIdx
	        });
	    },

	    dragLabelStart: function(evt, x, y) {

	        if (this.can('labelMove')) {

	            if (this.isDefaultInteractionPrevented(evt)) { return; }

	            var labelNode = evt.currentTarget;
	            var labelIdx = parseInt(labelNode.getAttribute('label-idx'), 10);

	            var defaultLabelPosition = this._getDefaultLabelPositionProperty();
	            var initialLabelPosition = this._normalizeLabelPosition(this._getLabelPositionProperty(labelIdx));
	            var position = this._mergeLabelPositionProperty(initialLabelPosition, defaultLabelPosition);

	            var coords = this.getLabelCoordinates(position);
	            var dx = coords.x - x; // how much needs to be added to cursor x to get to label x
	            var dy = coords.y - y; // how much needs to be added to cursor y to get to label y

	            var positionAngle = this._getLabelPositionAngle(labelIdx);
	            var labelPositionArgs = this._getLabelPositionArgs(labelIdx);
	            var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs();
	            var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs);

	            this.eventData(evt, {
	                action: 'label-move',
	                labelIdx: labelIdx,
	                dx: dx,
	                dy: dy,
	                positionAngle: positionAngle,
	                positionArgs: positionArgs,
	                stopPropagation: true
	            });

	        } else {

	            // Backwards compatibility:
	            // If labels can't be dragged no default action is triggered.
	            this.eventData(evt, { stopPropagation: true });
	        }

	        this.paper.delegateDragEvents(this, evt.data);
	    },

	    dragVertexStart: function(evt, x, y) {

	        if (!this.can('vertexMove')) { return; }

	        var vertexNode = evt.target;
	        var vertexIdx = parseInt(vertexNode.getAttribute('idx'), 10);
	        this.eventData(evt, {
	            action: 'vertex-move',
	            vertexIdx: vertexIdx
	        });
	    },

	    dragVertexRemoveStart: function(evt, x, y) {

	        if (!this.can('vertexRemove')) { return; }

	        var removeNode = evt.target;
	        var vertexIdx = parseInt(removeNode.getAttribute('idx'), 10);
	        this.model.removeVertex(vertexIdx);
	    },

	    dragArrowheadStart: function(evt, x, y) {

	        if (!this.can('arrowheadMove')) { return; }

	        var arrowheadNode = evt.target;
	        var arrowheadType = arrowheadNode.getAttribute('end');
	        var data = this.startArrowheadMove(arrowheadType, { ignoreBackwardsCompatibility: true });

	        this.eventData(evt, data);
	    },

	    dragStart: function(evt, x, y) {

	        if (this.isDefaultInteractionPrevented(evt)) { return; }

	        if (!this.can('linkMove')) { return; }

	        this.eventData(evt, {
	            action: 'move',
	            dx: x,
	            dy: y
	        });
	    },

	    // Drag Handlers
	    dragLabel: function(evt, x, y) {

	        var data = this.eventData(evt);
	        var label = { position: this.getLabelPosition((x + data.dx), (y + data.dy), data.positionAngle, data.positionArgs) };
	        if (this.paper.options.snapLabels) { delete label.position.offset; }
	        // The `touchmove' events are not fired
	        // when the original event target is removed from the DOM.
	        // The labels are currently re-rendered completely when only
	        // the position changes. This is why we need to make sure that
	        // the label is updated synchronously.
	        // TODO: replace `touchmove` with `pointermove` (breaking change).
	        var setOptions = { ui: true };
	        if (this.paper.isAsync() && evt.type === 'touchmove') {
	            setOptions.async = false;
	        }
	        this.model.label(data.labelIdx, label, setOptions);
	    },

	    dragVertex: function(evt, x, y) {

	        var data = this.eventData(evt);
	        this.model.vertex(data.vertexIdx, { x: x, y: y }, { ui: true });
	    },

	    dragArrowhead: function(evt, x, y) {
	        if (this.paper.options.snapLinks) {
	            var isSnapped = this._snapArrowhead(evt, x, y);
	            if (!isSnapped && this.paper.options.snapLinksSelf) {
	                this._snapArrowheadSelf(evt, x, y);
	            }
	        } else {
	            if (this.paper.options.snapLinksSelf) {
	                this._snapArrowheadSelf(evt, x, y);
	            } else {
	                this._connectArrowhead(this.getEventTarget(evt), x, y, this.eventData(evt));
	            }
	        }
	    },

	    drag: function(evt, x, y) {

	        var data = this.eventData(evt);
	        this.model.translate(x - data.dx, y - data.dy, { ui: true });
	        this.eventData(evt, {
	            dx: x,
	            dy: y
	        });
	    },

	    // Drag End Handlers

	    dragLabelEnd: function() {
	        // noop
	    },

	    dragVertexEnd: function() {
	        // noop
	    },

	    dragArrowheadEnd: function(evt, x, y) {

	        var data = this.eventData(evt);
	        var paper = this.paper;

	        if (paper.options.snapLinks) {
	            this._snapArrowheadEnd(data);
	        } else {
	            this._connectArrowheadEnd(data, x, y);
	        }

	        if (!paper.linkAllowed(this)) {
	            // If the changed link is not allowed, revert to its previous state.
	            this._disallow(data);
	        } else {
	            this._finishEmbedding(data);
	            this._notifyConnectEvent(data, evt);
	        }

	        this._afterArrowheadMove(data);
	    },

	    dragEnd: function() {
	        // noop
	    },

	    _disallow: function(data) {

	        switch (data.whenNotAllowed) {

	            case 'remove':
	                this.model.remove({ ui: true });
	                break;

	            case 'revert':
	            default:
	                this.model.set(data.arrowhead, data.initialEnd, { ui: true });
	                break;
	        }
	    },

	    _finishEmbedding: function(data) {

	        // Reparent the link if embedding is enabled
	        if (this.paper.options.embeddingMode && this.model.reparent()) {
	            // Make sure we don't reverse to the original 'z' index (see afterArrowheadMove()).
	            data.z = null;
	        }
	    },

	    _notifyConnectEvent: function(data, evt) {

	        var arrowhead = data.arrowhead;
	        var initialEnd = data.initialEnd;
	        var currentEnd = this.model.prop(arrowhead);
	        var endChanged = currentEnd && !Link.endsEqual(initialEnd, currentEnd);
	        if (endChanged) {
	            var paper = this.paper;
	            if (initialEnd.id) {
	                this.notify('link:disconnect', evt, paper.findViewByModel(initialEnd.id), data.initialMagnet, arrowhead);
	            }
	            if (currentEnd.id) {
	                this.notify('link:connect', evt, paper.findViewByModel(currentEnd.id), data.magnetUnderPointer, arrowhead);
	            }
	        }
	    },

	    _snapToPoints: function(snapPoint, points, radius) {
	        var closestPointX = null;
	        var closestDistanceX = Infinity;

	        var closestPointY = null;
	        var closestDistanceY = Infinity;

	        var x = snapPoint.x;
	        var y = snapPoint.y;

	        for (var i = 0; i < points.length; i++) {
	            var distX = Math.abs(points[i].x - snapPoint.x);
	            if (distX < closestDistanceX) {
	                closestDistanceX = distX;
	                closestPointX = points[i];
	            }

	            var distY = Math.abs(points[i].y - snapPoint.y);
	            if (distY < closestDistanceY) {
	                closestDistanceY = distY;
	                closestPointY = points[i];
	            }
	        }

	        if (closestDistanceX < radius) {
	            x = closestPointX.x;
	        }
	        if (closestDistanceY < radius) {
	            y = closestPointY.y;
	        }

	        return { x: x, y: y };
	    },

	    _snapArrowheadSelf: function(evt, x, y) {

	        var ref = this;
	        var paper = ref.paper;
	        var model = ref.model;
	        var ref$1 = paper.options;
	        var snapLinksSelf = ref$1.snapLinksSelf;
	        var data = this.eventData(evt);
	        var radius = snapLinksSelf.radius || 20;

	        var anchor = this.getEndAnchor(data.arrowhead === 'source' ? 'target' : 'source');
	        var vertices = model.vertices();
	        var points = [anchor ].concat( vertices);

	        var snapPoint = this._snapToPoints({ x: x, y: y }, points, radius);

	        this._connectArrowhead(document.elementFromPoint(snapPoint.x, snapPoint.y), snapPoint.x, snapPoint.y, this.eventData(evt));
	    },

	    _snapArrowhead: function(evt, x, y) {

	        var ref = this;
	        var paper = ref.paper;
	        var ref$1 = paper.options;
	        var snapLinks = ref$1.snapLinks;
	        var connectionStrategy = ref$1.connectionStrategy;
	        var data = this.eventData(evt);
	        var isSnapped = false;
	        // checking view in close area of the pointer

	        var r = snapLinks.radius || 50;
	        var viewsInArea = paper.findViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });

	        var prevClosestView = data.closestView || null;
	        var prevClosestMagnet = data.closestMagnet || null;
	        var prevMagnetProxy = data.magnetProxy || null;

	        data.closestView = data.closestMagnet = data.magnetProxy = null;

	        var minDistance = Number.MAX_VALUE;
	        var pointer = new Point(x, y);

	        viewsInArea.forEach(function(view) {
	            var candidates = [];
	            // skip connecting to the element in case '.': { magnet: false } attribute present
	            if (view.el.getAttribute('magnet') !== 'false') {
	                candidates.push({
	                    bbox: view.model.getBBox(),
	                    magnet: view.el
	                });
	            }

	            view.$('[magnet]').toArray().forEach(function (magnet) {
	                candidates.push({
	                    bbox: view.getNodeBBox(magnet),
	                    magnet: magnet
	                });
	            });

	            candidates.forEach(function (candidate) {
	                var magnet = candidate.magnet;
	                var bbox = candidate.bbox;
	                // find distance from the center of the model to pointer coordinates
	                var distance = bbox.center().squaredDistance(pointer);
	                // the connection is looked up in a circle area by `distance < r`
	                if (distance < minDistance) {
	                    var isAlreadyValidated = prevClosestMagnet === magnet;
	                    if (isAlreadyValidated || paper.options.validateConnection.apply(
	                        paper, data.validateConnectionArgs(view, (view.el === magnet) ? null : magnet)
	                    )) {
	                        minDistance = distance;
	                        data.closestView = view;
	                        data.closestMagnet = magnet;
	                    }
	                }
	            });

	        }, this);

	        var end;
	        var magnetProxy = null;
	        var closestView = data.closestView;
	        var closestMagnet = data.closestMagnet;
	        if (closestMagnet) {
	            magnetProxy = data.magnetProxy = closestView.findProxyNode(closestMagnet, 'highlighter');
	        }
	        var endType = data.arrowhead;
	        var newClosestMagnet = (prevClosestMagnet !== closestMagnet);
	        if (prevClosestView && newClosestMagnet) {
	            prevClosestView.unhighlight(prevMagnetProxy, {
	                connecting: true,
	                snapping: true
	            });
	        }

	        if (closestView) {
	            var prevEnd = data.prevEnd;
	            var prevX = data.prevX;
	            var prevY = data.prevY;
	            data.prevX = x;
	            data.prevY = y;
	            isSnapped = true;

	            if (!newClosestMagnet)  {
	                if (typeof connectionStrategy !== 'function' || (prevX === x && prevY === y)) {
	                    // the magnet has not changed and the link's end does not depend on the x and y
	                    return isSnapped;
	                }
	            }

	            end = closestView.getLinkEnd(closestMagnet, x, y, this.model, endType);
	            if (!newClosestMagnet && isEqual(prevEnd, end)) {
	                // the source/target json has not changed
	                return isSnapped;
	            }

	            data.prevEnd = end;

	            if (newClosestMagnet) {
	                closestView.highlight(magnetProxy, {
	                    connecting: true,
	                    snapping: true
	                });
	            }

	        } else {

	            end = { x: x, y: y };
	        }

	        this.model.set(endType, end || { x: x, y: y }, { ui: true });

	        if (prevClosestView) {
	            this.notify('link:snap:disconnect', evt, prevClosestView, prevClosestMagnet, endType);
	        }
	        if (closestView) {
	            this.notify('link:snap:connect', evt, closestView, closestMagnet, endType);
	        }

	        return isSnapped;
	    },

	    _snapArrowheadEnd: function(data) {

	        // Finish off link snapping.
	        // Everything except view unhighlighting was already done on pointermove.
	        var closestView = data.closestView;
	        var closestMagnet = data.closestMagnet;
	        if (closestView && closestMagnet) {

	            closestView.unhighlight(data.magnetProxy, { connecting: true, snapping: true });
	            data.magnetUnderPointer = closestView.findMagnet(closestMagnet);
	        }

	        data.closestView = data.closestMagnet = null;
	    },

	    _connectArrowhead: function(target, x, y, data) {

	        // checking views right under the pointer
	        var ref = this;
	        var paper = ref.paper;
	        var model = ref.model;

	        if (data.eventTarget !== target) {
	            // Unhighlight the previous view under pointer if there was one.
	            if (data.magnetProxy) {
	                data.viewUnderPointer.unhighlight(data.magnetProxy, {
	                    connecting: true
	                });
	            }

	            var viewUnderPointer = data.viewUnderPointer = paper.findView(target);
	            if (viewUnderPointer) {
	                // If we found a view that is under the pointer, we need to find the closest
	                // magnet based on the real target element of the event.
	                var magnetUnderPointer = data.magnetUnderPointer = viewUnderPointer.findMagnet(target);
	                var magnetProxy = data.magnetProxy = viewUnderPointer.findProxyNode(magnetUnderPointer, 'highlighter');

	                if (magnetUnderPointer && this.paper.options.validateConnection.apply(
	                    paper,
	                    data.validateConnectionArgs(viewUnderPointer, magnetUnderPointer)
	                )) {
	                    // If there was no magnet found, do not highlight anything and assume there
	                    // is no view under pointer we're interested in reconnecting to.
	                    // This can only happen if the overall element has the attribute `'.': { magnet: false }`.
	                    if (magnetProxy) {
	                        viewUnderPointer.highlight(magnetProxy, {
	                            connecting: true
	                        });
	                    }
	                } else {
	                    // This type of connection is not valid. Disregard this magnet.
	                    data.magnetUnderPointer = null;
	                    data.magnetProxy = null;
	                }
	            } else {
	                // Make sure we'll unset previous magnet.
	                data.magnetUnderPointer = null;
	                data.magnetProxy = null;
	            }
	        }

	        data.eventTarget = target;

	        model.set(data.arrowhead, { x: x, y: y }, { ui: true });
	    },

	    _connectArrowheadEnd: function(data, x, y) {
	        if ( data === void 0 ) data = {};


	        var ref = this;
	        var model = ref.model;
	        var viewUnderPointer = data.viewUnderPointer;
	        var magnetUnderPointer = data.magnetUnderPointer;
	        var magnetProxy = data.magnetProxy;
	        var arrowhead = data.arrowhead;

	        if (!magnetUnderPointer || !magnetProxy || !viewUnderPointer) { return; }

	        viewUnderPointer.unhighlight(magnetProxy, { connecting: true });

	        // The link end is taken from the magnet under the pointer, not the proxy.
	        var end = viewUnderPointer.getLinkEnd(magnetUnderPointer, x, y, model, arrowhead);
	        model.set(arrowhead, end, { ui: true });
	    },

	    _beforeArrowheadMove: function(data) {

	        data.z = this.model.get('z');
	        this.model.toFront();

	        // Let the pointer propagate through the link view elements so that
	        // the `evt.target` is another element under the pointer, not the link itself.
	        var style = this.el.style;
	        data.pointerEvents = style.pointerEvents;
	        style.pointerEvents = 'none';

	        if (this.paper.options.markAvailable) {
	            this._markAvailableMagnets(data);
	        }
	    },

	    _afterArrowheadMove: function(data) {

	        if (data.z !== null) {
	            this.model.set('z', data.z, { ui: true });
	            data.z = null;
	        }

	        // Put `pointer-events` back to its original value. See `_beforeArrowheadMove()` for explanation.
	        this.el.style.pointerEvents = data.pointerEvents;

	        if (this.paper.options.markAvailable) {
	            this._unmarkAvailableMagnets(data);
	        }
	    },

	    _createValidateConnectionArgs: function(arrowhead) {
	        // It makes sure the arguments for validateConnection have the following form:
	        // (source view, source magnet, target view, target magnet and link view)
	        var args = [];

	        args[4] = arrowhead;
	        args[5] = this;

	        var oppositeArrowhead;
	        var i = 0;
	        var j = 0;

	        if (arrowhead === 'source') {
	            i = 2;
	            oppositeArrowhead = 'target';
	        } else {
	            j = 2;
	            oppositeArrowhead = 'source';
	        }

	        var end = this.model.get(oppositeArrowhead);

	        if (end.id) {
	            var view = args[i] = this.paper.findViewByModel(end.id);
	            var magnet = view.getMagnetFromLinkEnd(end);
	            if (magnet === view.el) { magnet = undefined; }
	            args[i + 1] = magnet;
	        }

	        function validateConnectionArgs(cellView, magnet) {
	            args[j] = cellView;
	            args[j + 1] = cellView.el === magnet ? undefined : magnet;
	            return args;
	        }

	        return validateConnectionArgs;
	    },

	    _markAvailableMagnets: function(data) {

	        function isMagnetAvailable(view, magnet) {
	            var paper = view.paper;
	            var validate = paper.options.validateConnection;
	            return validate.apply(paper, this.validateConnectionArgs(view, magnet));
	        }

	        var paper = this.paper;
	        var elements = paper.model.getCells();
	        data.marked = {};

	        for (var i = 0, n = elements.length; i < n; i++) {
	            var view = elements[i].findView(paper);

	            if (!view) {
	                continue;
	            }

	            var magnets = Array.prototype.slice.call(view.el.querySelectorAll('[magnet]'));
	            if (view.el.getAttribute('magnet') !== 'false') {
	                // Element wrapping group is also a magnet
	                magnets.push(view.el);
	            }

	            var availableMagnets = magnets.filter(isMagnetAvailable.bind(data, view));

	            if (availableMagnets.length > 0) {
	                // highlight all available magnets
	                for (var j = 0, m = availableMagnets.length; j < m; j++) {
	                    view.highlight(availableMagnets[j], { magnetAvailability: true });
	                }
	                // highlight the entire view
	                view.highlight(null, { elementAvailability: true });

	                data.marked[view.model.id] = availableMagnets;
	            }
	        }
	    },

	    _unmarkAvailableMagnets: function(data) {

	        var markedKeys = Object.keys(data.marked);
	        var id;
	        var markedMagnets;

	        for (var i = 0, n = markedKeys.length; i < n; i++) {
	            id = markedKeys[i];
	            markedMagnets = data.marked[id];

	            var view = this.paper.findViewByModel(id);
	            if (view) {
	                for (var j = 0, m = markedMagnets.length; j < m; j++) {
	                    view.unhighlight(markedMagnets[j], { magnetAvailability: true });
	                }
	                view.unhighlight(null, { elementAvailability: true });
	            }
	        }

	        data.marked = null;
	    },

	    startArrowheadMove: function(end, opt) {

	        opt || (opt = {});

	        // Allow to delegate events from an another view to this linkView in order to trigger arrowhead
	        // move without need to click on the actual arrowhead dom element.
	        var data = {
	            action: 'arrowhead-move',
	            arrowhead: end,
	            whenNotAllowed: opt.whenNotAllowed || 'revert',
	            initialMagnet: this[end + 'Magnet'] || (this[end + 'View'] ? this[end + 'View'].el : null),
	            initialEnd: clone(this.model.get(end)),
	            validateConnectionArgs: this._createValidateConnectionArgs(end)
	        };

	        this._beforeArrowheadMove(data);

	        if (opt.ignoreBackwardsCompatibility !== true) {
	            this._dragData = data;
	        }

	        return data;
	    },

	    // Lifecycle methods

	    onMount: function() {
	        CellView.prototype.onMount.apply(this, arguments);
	        this.mountLabels();
	    },

	    onDetach: function() {
	        CellView.prototype.onDetach.apply(this, arguments);
	        this.unmountLabels();
	    },

	    onRemove: function() {
	        CellView.prototype.onRemove.apply(this, arguments);
	        this.unmountLabels();
	    }

	}, {

	    Flags: Flags$2,
	});

	Object.defineProperty(LinkView.prototype, 'sourceBBox', {

	    enumerable: true,

	    get: function() {
	        var sourceView = this.sourceView;
	        if (!sourceView) {
	            var sourceDef = this.model.source();
	            return new Rect(sourceDef.x, sourceDef.y);
	        }
	        var sourceMagnet = this.sourceMagnet;
	        if (sourceView.isNodeConnection(sourceMagnet)) {
	            return new Rect(this.sourceAnchor);
	        }
	        return sourceView.getNodeBBox(sourceMagnet || sourceView.el);
	    }

	});

	Object.defineProperty(LinkView.prototype, 'targetBBox', {

	    enumerable: true,

	    get: function() {
	        var targetView = this.targetView;
	        if (!targetView) {
	            var targetDef = this.model.target();
	            return new Rect(targetDef.x, targetDef.y);
	        }
	        var targetMagnet = this.targetMagnet;
	        if (targetView.isNodeConnection(targetMagnet)) {
	            return new Rect(this.targetAnchor);
	        }
	        return targetView.getNodeBBox(targetMagnet || targetView.el);
	    }
	});

	var stroke = HighlighterView.extend({

	    tagName: 'path',
	    className: 'highlight-stroke',
	    attributes: {
	        'pointer-events': 'none',
	        'vector-effect': 'non-scaling-stroke',
	        'fill': 'none'
	    },

	    options: {
	        padding: 3,
	        rx: 0,
	        ry: 0,
	        useFirstSubpath: false,
	        attrs: {
	            'stroke-width': 3,
	            'stroke': '#FEB663'
	        }
	    },

	    getPathData: function getPathData(cellView, node) {
	        var ref = this;
	        var options = ref.options;
	        var useFirstSubpath = options.useFirstSubpath;
	        var d;
	        try {
	            var vNode = V(node);
	            d = vNode.convertToPathData().trim();
	            if (vNode.tagName() === 'PATH' && useFirstSubpath) {
	                var secondSubpathIndex = d.search(/.M/i) + 1;
	                if (secondSubpathIndex > 0) {
	                    d = d.substr(0, secondSubpathIndex);
	                }
	            }
	        } catch (error) {
	            // Failed to get path data from magnet element.
	            // Draw a rectangle around the node instead.
	            var nodeBBox = cellView.getNodeBoundingRect(node);
	            d = V.rectToPath(assign({}, options, nodeBBox.toJSON()));
	        }
	        return d;
	    },

	    highlightConnection: function highlightConnection(cellView) {
	        this.vel.attr('d', cellView.getSerializedConnection());
	    },

	    highlightNode: function highlightNode(cellView, node) {
	        var ref = this;
	        var vel = ref.vel;
	        var options = ref.options;
	        var padding = options.padding;
	        var layer = options.layer;
	        var highlightMatrix = this.getNodeMatrix(cellView, node);
	        // Add padding to the highlight element.
	        if (padding) {
	            if (!layer && node === cellView.el) {
	                // If the highlighter is appended to the cellView
	                // and we measure the size of the cellView wrapping group
	                // it's necessary to remove the highlighter first
	                vel.remove();
	            }
	            var nodeBBox = cellView.getNodeBoundingRect(node);
	            var cx = nodeBBox.x + (nodeBBox.width / 2);
	            var cy = nodeBBox.y + (nodeBBox.height / 2);
	            nodeBBox = V.transformRect(nodeBBox, highlightMatrix);
	            var width = Math.max(nodeBBox.width, 1);
	            var height = Math.max(nodeBBox.height, 1);
	            var sx = (width + padding) / width;
	            var sy = (height + padding) / height;
	            var paddingMatrix = V.createSVGMatrix({
	                a: sx,
	                b: 0,
	                c: 0,
	                d: sy,
	                e: cx - sx * cx,
	                f: cy - sy * cy
	            });
	            highlightMatrix = highlightMatrix.multiply(paddingMatrix);
	        }
	        vel.attr({
	            'd': this.getPathData(cellView, node),
	            'transform': V.matrixToTransformString(highlightMatrix)
	        });
	    },

	    highlight: function highlight(cellView, node) {
	        var ref = this;
	        var vel = ref.vel;
	        var options = ref.options;
	        vel.attr(options.attrs);
	        if (cellView.isNodeConnection(node)) {
	            this.highlightConnection(cellView);
	        } else {
	            this.highlightNode(cellView, node);
	        }
	    }

	});

	var MASK_CLIP = 20;

	function forEachDescendant(vel, fn) {
	    var descendants = vel.children();
	    while (descendants.length > 0) {
	        var descendant = descendants.shift();
	        if (fn(descendant)) {
	            descendants.push.apply(descendants, descendant.children());
	        }
	    }
	}

	var mask = HighlighterView.extend({

	    tagName: 'rect',
	    className: 'highlight-mask',
	    attributes: {
	        'pointer-events': 'none'
	    },

	    options: {
	        padding: 3,
	        maskClip: MASK_CLIP,
	        deep: false,
	        attrs: {
	            'stroke': '#FEB663',
	            'stroke-width': 3,
	            'stroke-linecap': 'butt',
	            'stroke-linejoin': 'miter',
	        }
	    },

	    VISIBLE: 'white',
	    INVISIBLE: 'black',

	    MASK_ROOT_ATTRIBUTE_BLACKLIST: [
	        'marker-start',
	        'marker-end',
	        'marker-mid',
	        'transform',
	        'stroke-dasharray',
	        'class' ],

	    MASK_CHILD_ATTRIBUTE_BLACKLIST: [
	        'stroke',
	        'fill',
	        'stroke-width',
	        'stroke-opacity',
	        'stroke-dasharray',
	        'fill-opacity',
	        'marker-start',
	        'marker-end',
	        'marker-mid',
	        'class' ],

	    // TODO: change the list to a function callback
	    MASK_REPLACE_TAGS: [
	        'FOREIGNOBJECT',
	        'IMAGE',
	        'USE',
	        'TEXT',
	        'TSPAN',
	        'TEXTPATH'
	    ],

	    // TODO: change the list to a function callback
	    MASK_REMOVE_TAGS: [
	        'TEXT',
	        'TSPAN',
	        'TEXTPATH'
	    ],

	    transformMaskChild: function transformMaskChild(cellView, childEl) {
	        var ref = this;
	        var MASK_CHILD_ATTRIBUTE_BLACKLIST = ref.MASK_CHILD_ATTRIBUTE_BLACKLIST;
	        var MASK_REPLACE_TAGS = ref.MASK_REPLACE_TAGS;
	        var MASK_REMOVE_TAGS = ref.MASK_REMOVE_TAGS;
	        var childTagName = childEl.tagName();
	        // Do not include the element in the mask's image
	        if (!V.isSVGGraphicsElement(childEl) || MASK_REMOVE_TAGS.includes(childTagName)) {
	            childEl.remove();
	            return false;
	        }
	        // Replace the element with a rectangle
	        if (MASK_REPLACE_TAGS.includes(childTagName)) {
	            // Note: clone() method does not change the children ids
	            var originalChild = cellView.vel.findOne(("#" + (childEl.id)));
	            if (originalChild) {
	                var originalNode = originalChild.node;
	                var childBBox = cellView.getNodeBoundingRect(originalNode);
	                if (cellView.model.isElement()) {
	                    childBBox = V.transformRect(childBBox, cellView.getNodeMatrix(originalNode));
	                }
	                var replacement = V('rect', childBBox.toJSON());
	                var ref$1 = childBBox.center();
	                var ox = ref$1.x;
	                var oy = ref$1.y;
	                var ref$2 = originalChild.rotate();
	                var angle = ref$2.angle;
	                var cx = ref$2.cx; if ( cx === void 0 ) cx = ox;
	                var cy = ref$2.cy; if ( cy === void 0 ) cy = oy;
	                if (angle) { replacement.rotate(angle, cx, cy); }
	                // Note: it's not important to keep the same sibling index since all subnodes are filled
	                childEl.parent().append(replacement);
	            }
	            childEl.remove();
	            return false;
	        }
	        // Keep the element, but clean it from certain attributes
	        MASK_CHILD_ATTRIBUTE_BLACKLIST.forEach(function (attrName) {
	            if (attrName === 'fill' && childEl.attr('fill') === 'none') { return; }
	            childEl.removeAttr(attrName);
	        });
	        return true;
	    },

	    transformMaskRoot: function transformMaskRoot(_cellView, rootEl) {
	        var ref = this;
	        var MASK_ROOT_ATTRIBUTE_BLACKLIST = ref.MASK_ROOT_ATTRIBUTE_BLACKLIST;
	        MASK_ROOT_ATTRIBUTE_BLACKLIST.forEach(function (attrName) {
	            rootEl.removeAttr(attrName);
	        });
	    },

	    getMaskShape: function getMaskShape(cellView, vel) {
	        var this$1 = this;

	        var ref = this;
	        var options = ref.options;
	        var MASK_REPLACE_TAGS = ref.MASK_REPLACE_TAGS;
	        var deep = options.deep;
	        var tagName = vel.tagName();
	        var maskRoot;
	        if (tagName === 'G') {
	            if (!deep) { return null; }
	            maskRoot = vel.clone();
	            forEachDescendant(maskRoot, function (maskChild) { return this$1.transformMaskChild(cellView, maskChild); });
	        } else {
	            if (MASK_REPLACE_TAGS.includes(tagName)) { return null; }
	            maskRoot = vel.clone();
	        }
	        this.transformMaskRoot(cellView, maskRoot);
	        return maskRoot;
	    },

	    getMaskId: function getMaskId() {
	        return ("highlight-mask-" + (this.cid));
	    },

	    getMask: function getMask(cellView, vNode) {

	        var ref = this;
	        var VISIBLE = ref.VISIBLE;
	        var INVISIBLE = ref.INVISIBLE;
	        var options = ref.options;
	        var padding = options.padding;
	        var attrs = options.attrs;

	        var strokeWidth = ('stroke-width' in attrs) ? attrs['stroke-width'] : 1;
	        var hasNodeFill = vNode.attr('fill') !== 'none';
	        var magnetStrokeWidth = parseFloat(vNode.attr('stroke-width'));
	        if (isNaN(magnetStrokeWidth)) { magnetStrokeWidth = 1; }
	        // stroke of the invisible shape
	        var minStrokeWidth = magnetStrokeWidth + padding * 2;
	        // stroke of the visible shape
	        var maxStrokeWidth = minStrokeWidth + strokeWidth * 2;
	        var maskEl = this.getMaskShape(cellView, vNode);
	        if (!maskEl) {
	            var nodeBBox = cellView.getNodeBoundingRect(vNode.node);
	            // Make sure the rect is visible
	            nodeBBox.inflate(nodeBBox.width ? 0 : 0.5, nodeBBox.height ? 0 : 0.5);
	            maskEl =  V('rect', nodeBBox.toJSON());
	        }
	        maskEl.attr(attrs);
	        return V('mask', {
	            'id': this.getMaskId()
	        }).append([
	            maskEl.clone().attr({
	                'fill': hasNodeFill ? VISIBLE : 'none',
	                'stroke': VISIBLE,
	                'stroke-width': maxStrokeWidth
	            }),
	            maskEl.clone().attr({
	                'fill': hasNodeFill ? INVISIBLE : 'none',
	                'stroke': INVISIBLE,
	                'stroke-width': minStrokeWidth
	            })
	        ]);
	    },

	    removeMask: function removeMask(paper) {
	        var maskNode = paper.svg.getElementById(this.getMaskId());
	        if (maskNode) {
	            paper.defs.removeChild(maskNode);
	        }
	    },

	    addMask: function addMask(paper, maskEl) {
	        paper.defs.appendChild(maskEl.node);
	    },

	    highlight: function highlight(cellView, node) {
	        var ref = this;
	        var options = ref.options;
	        var vel = ref.vel;
	        var padding = options.padding;
	        var attrs = options.attrs;
	        var maskClip = options.maskClip; if ( maskClip === void 0 ) maskClip = MASK_CLIP;
	        var layer = options.layer;
	        var color = ('stroke' in attrs) ? attrs['stroke'] : '#000000';
	        if (!layer && node === cellView.el) {
	            // If the highlighter is appended to the cellView
	            // and we measure the size of the cellView wrapping group
	            // it's necessary to remove the highlighter first
	            vel.remove();
	        }
	        var highlighterBBox = cellView.getNodeBoundingRect(node).inflate(padding + maskClip);
	        var highlightMatrix = this.getNodeMatrix(cellView, node);
	        var maskEl = this.getMask(cellView, V(node));
	        this.addMask(cellView.paper, maskEl);
	        vel.attr(highlighterBBox.toJSON());
	        vel.attr({
	            'transform': V.matrixToTransformString(highlightMatrix),
	            'mask': ("url(#" + (maskEl.id) + ")"),
	            'fill': color
	        });
	    },

	    unhighlight: function unhighlight(cellView) {
	        this.removeMask(cellView.paper);
	    }

	});

	var opacity = HighlighterView.extend({

	    UPDATABLE: false,
	    MOUNTABLE: false,

	    opacityClassName: addClassNamePrefix('highlight-opacity'),

	    highlight: function(_cellView, node) {
	        V(node).addClass(this.opacityClassName);
	    },

	    unhighlight: function(_cellView, node) {
	        V(node).removeClass(this.opacityClassName);
	    }

	});

	var className = addClassNamePrefix('highlighted');

	var addClass = HighlighterView.extend({

	    UPDATABLE: false,
	    MOUNTABLE: false,

	    options: {
	        className: className
	    },

	    highlight: function(_cellView, node) {
	        V(node).addClass(this.options.className);
	    },

	    unhighlight: function(_cellView, node) {
	        V(node).removeClass(this.options.className);
	    }

	}, {
	    // Backwards Compatibility
	    className: className
	});

	var Directions$2 = {
	    ROW: 'row',
	    COLUMN: 'column'
	};

	var list = HighlighterView.extend({

	    tagName: 'g',
	    MOUNTABLE: true,
	    UPDATE_ATTRIBUTES: function() {
	        return [this.options.attribute];
	    },

	    _prevItems: null,

	    highlight: function highlight(elementView, node) {
	        var this$1 = this;

	        var element = elementView.model;
	        var ref = this.options;
	        var attribute = ref.attribute;
	        var size = ref.size; if ( size === void 0 ) size = 20;
	        var gap = ref.gap; if ( gap === void 0 ) gap = 5;
	        var direction = ref.direction; if ( direction === void 0 ) direction = Directions$2.ROW;
	        if (!attribute) { throw new Error('List: attribute is required'); }
	        var normalizedSize = (typeof size === 'number') ? { width: size, height: size } : size;
	        var isRowDirection = (direction === Directions$2.ROW);
	        var itemWidth = isRowDirection ? normalizedSize.width : normalizedSize.height;
	        var items = element.get(attribute);
	        if (!Array.isArray(items)) { items = []; }
	        var prevItems = this._prevItems || [];
	        var comparison = items.map(function (item, index) { return isEqual(prevItems[index], items[index]); });
	        if (prevItems.length !== items.length || comparison.some(function (unchanged) { return !unchanged; })) {
	            var prevEls = this.vel.children();
	            var itemsEls = items.map(function (item, index) {
	                var prevEl = (index in prevEls) ? prevEls[index].node : null;
	                if (comparison[index]) { return prevEl; }
	                var itemEl = this$1.createListItem(item, normalizedSize, prevEl);
	                if (!itemEl) { return null; }
	                if (!(itemEl instanceof SVGElement)) { throw new Error('List: item must be an SVGElement'); }
	                itemEl.dataset.index = index;
	                itemEl.dataset.attribute = attribute;
	                var offset = index * (itemWidth + gap);
	                itemEl.setAttribute('transform', (isRowDirection)
	                    ? ("translate(" + offset + ", 0)")
	                    : ("translate(0, " + offset + ")")
	                );
	                return itemEl;
	            });
	            this.vel.empty().append(itemsEls);
	            this._prevItems = items;
	        }
	        var itemsCount = items.length;
	        var length = (itemsCount === 0)
	            ? 0
	            : (itemsCount * itemWidth + (itemsCount - 1) * gap);
	        var listSize = (isRowDirection)
	            ? { width: length, height: normalizedSize.height }
	            : { width: normalizedSize.width, height: length };

	        this.position(element, listSize);
	    },

	    position: function position(element, listSize) {
	        var ref = this;
	        var vel = ref.vel;
	        var options = ref.options;
	        var margin = options.margin; if ( margin === void 0 ) margin = 5;
	        var position = options.position; if ( position === void 0 ) position = 'top-left';
	        var ref$1 = element.size();
	        var width = ref$1.width;
	        var height = ref$1.height;
	        var ref$2 = normalizeSides(margin);
	        var left = ref$2.left;
	        var right = ref$2.right;
	        var top = ref$2.top;
	        var bottom = ref$2.bottom;
	        var bbox = new Rect(left, top, width - (left + right), height - (top + bottom));
	        var ref$3 = getRectPoint(bbox, position);
	        var x = ref$3.x;
	        var y = ref$3.y;
	        // x
	        switch (position) {
	            case Positions.CENTER:
	            case Positions.TOP:
	            case Positions.BOTTOM: {
	                x -= listSize.width / 2;
	                break;
	            }
	            case Positions.RIGHT:
	            case Positions.BOTTOM_RIGHT:
	            case Positions.TOP_RIGHT: {
	                x -= listSize.width;
	                break;
	            }
	        }
	        // y
	        switch (position) {
	            case Positions.CENTER:
	            case Positions.RIGHT:
	            case Positions.LEFT: {
	                y -= listSize.height / 2;
	                break;
	            }
	            case Positions.BOTTOM:
	            case Positions.BOTTOM_RIGHT:
	            case Positions.BOTTOM_LEFT: {
	                y -= listSize.height;
	                break;
	            }
	        }
	        vel.attr('transform', ("translate(" + x + ", " + y + ")"));
	    }
	}, {
	    Directions: Directions$2,
	    Positions: Positions
	});



	var highlighters = ({
		stroke: stroke,
		mask: mask,
		opacity: opacity,
		addClass: addClass,
		list: list
	});

	function connectionRatio(view, _magnet, _refPoint, opt) {

	    var ratio = ('ratio' in opt) ? opt.ratio : 0.5;
	    return view.getPointAtRatio(ratio);
	}

	function connectionLength(view, _magnet, _refPoint, opt) {

	    var length = ('length' in opt) ? opt.length : 20;
	    return view.getPointAtLength(length);
	}

	function _connectionPerpendicular(view, _magnet, refPoint, opt) {

	    var OFFSET = 1e6;
	    var path = view.getConnection();
	    var segmentSubdivisions = view.getConnectionSubdivisions();
	    var verticalLine = new Line(refPoint.clone().offset(0, OFFSET), refPoint.clone().offset(0, -OFFSET));
	    var horizontalLine = new Line(refPoint.clone().offset(OFFSET, 0), refPoint.clone().offset(-OFFSET, 0));
	    var verticalIntersections = verticalLine.intersect(path, { segmentSubdivisions: segmentSubdivisions });
	    var horizontalIntersections = horizontalLine.intersect(path, { segmentSubdivisions: segmentSubdivisions });
	    var intersections = [];
	    if (verticalIntersections) { Array.prototype.push.apply(intersections, verticalIntersections); }
	    if (horizontalIntersections) { Array.prototype.push.apply(intersections, horizontalIntersections); }
	    if (intersections.length > 0) { return refPoint.chooseClosest(intersections); }
	    if ('fallbackAt' in opt) {
	        return getPointAtLink(view, opt.fallbackAt);
	    }
	    return connectionClosest(view, _magnet, refPoint, opt);
	}

	function _connectionClosest(view, _magnet, refPoint, _opt) {

	    var closestPoint = view.getClosestPoint(refPoint);
	    if (!closestPoint) { return new Point(); }
	    return closestPoint;
	}

	function resolveRef(fn) {
	    return function(view, magnet, ref, opt) {
	        if (ref instanceof Element) {
	            var refView = this.paper.findView(ref);
	            var refPoint;
	            if (refView) {
	                if (refView.isNodeConnection(ref)) {
	                    var distance = ('fixedAt' in opt) ? opt.fixedAt : '50%';
	                    refPoint = getPointAtLink(refView, distance);
	                } else {
	                    refPoint = refView.getNodeBBox(ref).center();
	                }
	            } else {
	                // Something went wrong
	                refPoint = new Point();
	            }
	            return fn.call(this, view, magnet, refPoint, opt);
	        }
	        return fn.apply(this, arguments);
	    };
	}

	function getPointAtLink(view, value) {
	    var parsedValue = parseFloat(value);
	    if (isPercentage(value)) {
	        return view.getPointAtRatio(parsedValue / 100);
	    } else {
	        return view.getPointAtLength(parsedValue);
	    }
	}
	var connectionPerpendicular = resolveRef(_connectionPerpendicular);
	var connectionClosest = resolveRef(_connectionClosest);

	var linkAnchors = ({
		resolveRef: resolveRef,
		connectionRatio: connectionRatio,
		connectionLength: connectionLength,
		connectionPerpendicular: connectionPerpendicular,
		connectionClosest: connectionClosest
	});

	function offsetPoint(p1, p2, offset) {
	    if (isPlainObject(offset)) {
	        var x = offset.x;
	        var y = offset.y;
	        if (isFinite(y)) {
	            var line =  new Line(p2, p1);
	            var ref = line.parallel(y);
	            var start = ref.start;
	            var end = ref.end;
	            p2 = start;
	            p1 = end;
	        }
	        offset = x;
	    }
	    if (!isFinite(offset)) { return p1; }
	    var length = p1.distance(p2);
	    if (offset === 0 && length > 0) { return p1; }
	    return p1.move(p2, -Math.min(offset, length - 1));
	}

	function stroke$1(magnet) {

	    var stroke = magnet.getAttribute('stroke-width');
	    if (stroke === null) { return 0; }
	    return parseFloat(stroke) || 0;
	}

	function alignLine(line, type, offset) {
	    if ( offset === void 0 ) offset = 0;

	    var coordinate, a, b, direction;
	    var start = line.start;
	    var end = line.end;
	    switch (type) {
	        case 'left':
	            coordinate = 'x';
	            a = end;
	            b = start;
	            direction = -1;
	            break;
	        case 'right':
	            coordinate = 'x';
	            a = start;
	            b = end;
	            direction = 1;
	            break;
	        case 'top':
	            coordinate = 'y';
	            a = end;
	            b = start;
	            direction = -1;
	            break;
	        case 'bottom':
	            coordinate = 'y';
	            a = start;
	            b = end;
	            direction = 1;
	            break;
	        default:
	            return;
	    }
	    if (start[coordinate] < end[coordinate]) {
	        a[coordinate] = b[coordinate];
	    } else {
	        b[coordinate] = a[coordinate];
	    }
	    if (isFinite(offset)) {
	        a[coordinate] += direction * offset;
	        b[coordinate] += direction * offset;
	    }
	}

	// Connection Points

	function anchorConnectionPoint(line, _view, _magnet, opt) {
	    var offset = opt.offset;
	    var alignOffset = opt.alignOffset;
	    var align = opt.align;
	    if (align) { alignLine(line, align, alignOffset); }
	    return offsetPoint(line.end, line.start, offset);
	}

	function bboxIntersection(line, view, magnet, opt) {

	    var bbox = view.getNodeBBox(magnet);
	    if (opt.stroke) { bbox.inflate(stroke$1(magnet) / 2); }
	    var intersections = line.intersect(bbox);
	    var cp = (intersections)
	        ? line.start.chooseClosest(intersections)
	        : line.end;
	    return offsetPoint(cp, line.start, opt.offset);
	}

	function rectangleIntersection(line, view, magnet, opt) {

	    var angle = view.model.angle();
	    if (angle === 0) {
	        return bboxIntersection(line, view, magnet, opt);
	    }

	    var bboxWORotation = view.getNodeUnrotatedBBox(magnet);
	    if (opt.stroke) { bboxWORotation.inflate(stroke$1(magnet) / 2); }
	    var center = bboxWORotation.center();
	    var lineWORotation = line.clone().rotate(center, angle);
	    var intersections = lineWORotation.setLength(1e6).intersect(bboxWORotation);
	    var cp = (intersections)
	        ? lineWORotation.start.chooseClosest(intersections).rotate(center, -angle)
	        : line.end;
	    return offsetPoint(cp, line.start, opt.offset);
	}

	function findShapeNode(magnet) {
	    if (!magnet) { return null; }
	    var node = magnet;
	    do {
	        var tagName = node.tagName;
	        if (typeof tagName !== 'string') { return null; }
	        tagName = tagName.toUpperCase();
	        if (tagName === 'G') {
	            node = node.firstElementChild;
	        } else if (tagName === 'TITLE') {
	            node = node.nextElementSibling;
	        } else { break; }
	    } while (node);
	    return node;
	}

	var BNDR_SUBDIVISIONS = 'segmentSubdivisons';
	var BNDR_SHAPE_BBOX = 'shapeBBox';

	function boundaryIntersection(line, view, magnet, opt) {

	    var node, intersection;
	    var selector = opt.selector;
	    var anchor = line.end;

	    if (typeof selector === 'string') {
	        node = view.findBySelector(selector)[0];
	    } else if (selector === false) {
	        node = magnet;
	    } else if (Array.isArray(selector)) {
	        node = getByPath(magnet, selector);
	    } else {
	        node = findShapeNode(magnet);
	    }

	    if (!V.isSVGGraphicsElement(node)) {
	        if (node === magnet || !V.isSVGGraphicsElement(magnet)) { return anchor; }
	        node = magnet;
	    }

	    var localShape = view.getNodeShape(node);
	    var magnetMatrix = view.getNodeMatrix(node);
	    var translateMatrix = view.getRootTranslateMatrix();
	    var rotateMatrix = view.getRootRotateMatrix();
	    var targetMatrix = translateMatrix.multiply(rotateMatrix).multiply(magnetMatrix);
	    var localMatrix = targetMatrix.inverse();
	    var localLine = V.transformLine(line, localMatrix);
	    var localRef = localLine.start.clone();
	    var data = view.getNodeData(node);

	    if (opt.insideout === false) {
	        if (!data[BNDR_SHAPE_BBOX]) { data[BNDR_SHAPE_BBOX] = localShape.bbox(); }
	        var localBBox = data[BNDR_SHAPE_BBOX];
	        if (localBBox.containsPoint(localRef)) { return anchor; }
	    }

	    // Caching segment subdivisions for paths
	    var pathOpt;
	    if (localShape instanceof Path) {
	        var precision = opt.precision || 2;
	        if (!data[BNDR_SUBDIVISIONS]) { data[BNDR_SUBDIVISIONS] = localShape.getSegmentSubdivisions({ precision: precision }); }
	        pathOpt = {
	            precision: precision,
	            segmentSubdivisions: data[BNDR_SUBDIVISIONS]
	        };
	    }

	    if (opt.extrapolate === true) { localLine.setLength(1e6); }

	    intersection = localLine.intersect(localShape, pathOpt);
	    if (intersection) {
	        // More than one intersection
	        if (V.isArray(intersection)) { intersection = localRef.chooseClosest(intersection); }
	    } else if (opt.sticky === true) {
	        // No intersection, find the closest point instead
	        if (localShape instanceof Rect) {
	            intersection = localShape.pointNearestToPoint(localRef);
	        } else if (localShape instanceof Ellipse) {
	            intersection = localShape.intersectionWithLineFromCenterToPoint(localRef);
	        } else {
	            intersection = localShape.closestPoint(localRef, pathOpt);
	        }
	    }

	    var cp = (intersection) ? V.transformPoint(intersection, targetMatrix) : anchor;
	    var cpOffset = opt.offset || 0;
	    if (opt.stroke) { cpOffset += stroke$1(node) / 2; }

	    return offsetPoint(cp, line.start, cpOffset);
	}

	var anchor = anchorConnectionPoint;
	var bbox = bboxIntersection;
	var rectangle = rectangleIntersection;
	var boundary = boundaryIntersection;

	var connectionPoints = ({
		anchor: anchor,
		bbox: bbox,
		rectangle: rectangle,
		boundary: boundary
	});

	function bboxWrapper(method) {

	    return function(view, magnet, ref, opt) {

	        var rotate = !!opt.rotate;
	        var bbox = (rotate) ? view.getNodeUnrotatedBBox(magnet) : view.getNodeBBox(magnet);
	        var anchor = bbox[method]();

	        var dx = opt.dx;
	        if (dx) {
	            var dxPercentage = isPercentage(dx);
	            dx = parseFloat(dx);
	            if (isFinite(dx)) {
	                if (dxPercentage) {
	                    dx /= 100;
	                    dx *= bbox.width;
	                }
	                anchor.x += dx;
	            }
	        }

	        var dy = opt.dy;
	        if (dy) {
	            var dyPercentage = isPercentage(dy);
	            dy = parseFloat(dy);
	            if (isFinite(dy)) {
	                if (dyPercentage) {
	                    dy /= 100;
	                    dy *= bbox.height;
	                }
	                anchor.y += dy;
	            }
	        }

	        return (rotate) ? anchor.rotate(view.model.getBBox().center(), -view.model.angle()) : anchor;
	    };
	}

	function _perpendicular(view, magnet, refPoint, opt) {

	    var angle = view.model.angle();
	    var bbox = view.getNodeBBox(magnet);
	    var anchor = bbox.center();
	    var topLeft = bbox.origin();
	    var bottomRight = bbox.corner();

	    var padding = opt.padding;
	    if (!isFinite(padding)) { padding = 0; }

	    if ((topLeft.y + padding) <= refPoint.y && refPoint.y <= (bottomRight.y - padding)) {
	        var dy = (refPoint.y - anchor.y);
	        anchor.x += (angle === 0 || angle === 180) ? 0 : dy * 1 / Math.tan(toRad(angle));
	        anchor.y += dy;
	    } else if ((topLeft.x + padding) <= refPoint.x && refPoint.x <= (bottomRight.x - padding)) {
	        var dx = (refPoint.x - anchor.x);
	        anchor.y += (angle === 90 || angle === 270) ? 0 : dx * Math.tan(toRad(angle));
	        anchor.x += dx;
	    }

	    return anchor;
	}

	function _midSide(view, magnet, refPoint, opt) {

	    var rotate = !!opt.rotate;
	    var bbox, angle, center;
	    if (rotate) {
	        bbox = view.getNodeUnrotatedBBox(magnet);
	        center = view.model.getBBox().center();
	        angle = view.model.angle();
	    } else {
	        bbox = view.getNodeBBox(magnet);
	    }

	    var padding = opt.padding;
	    if (isFinite(padding)) { bbox.inflate(padding); }

	    if (rotate) { refPoint.rotate(center, angle); }

	    var side = bbox.sideNearestToPoint(refPoint);
	    var anchor;
	    switch (side) {
	        case 'left':
	            anchor = bbox.leftMiddle();
	            break;
	        case 'right':
	            anchor = bbox.rightMiddle();
	            break;
	        case 'top':
	            anchor = bbox.topMiddle();
	            break;
	        case 'bottom':
	            anchor = bbox.bottomMiddle();
	            break;
	    }

	    return (rotate) ? anchor.rotate(center, -angle) : anchor;
	}

	// Can find anchor from model, when there is no selector or the link end
	// is connected to a port
	function _modelCenter(view, _magnet, _refPoint, opt, endType) {
	    return view.model.getPointFromConnectedLink(this.model, endType).offset(opt.dx, opt.dy);
	}

	//joint.anchors
	var center = bboxWrapper('center');
	var top$2 = bboxWrapper('topMiddle');
	var bottom$2 = bboxWrapper('bottomMiddle');
	var left$2 = bboxWrapper('leftMiddle');
	var right$2 = bboxWrapper('rightMiddle');
	var topLeft = bboxWrapper('origin');
	var topRight = bboxWrapper('topRight');
	var bottomLeft = bboxWrapper('bottomLeft');
	var bottomRight = bboxWrapper('corner');
	var perpendicular = resolveRef(_perpendicular);
	var midSide = resolveRef(_midSide);
	var modelCenter = _modelCenter;

	var anchors = ({
		center: center,
		top: top$2,
		bottom: bottom$2,
		left: left$2,
		right: right$2,
		topLeft: topLeft,
		topRight: topRight,
		bottomLeft: bottomLeft,
		bottomRight: bottomRight,
		perpendicular: perpendicular,
		midSide: midSide,
		modelCenter: modelCenter
	});

	var sortingTypes = {
	    NONE: 'sorting-none',
	    APPROX: 'sorting-approximate',
	    EXACT: 'sorting-exact'
	};

	var WHEEL_CAP = 50;
	var WHEEL_WAIT_MS = 20;
	var MOUNT_BATCH_SIZE = 1000;
	var UPDATE_BATCH_SIZE = Infinity;
	var MIN_PRIORITY = 9007199254740991; // Number.MAX_SAFE_INTEGER

	var HighlightingTypes$1 = CellView.Highlighting;

	var defaultHighlighting = {};
	defaultHighlighting[HighlightingTypes$1.DEFAULT] = {
	        name: 'stroke',
	        options: {
	            padding: 3
	        }
	    };
	defaultHighlighting[HighlightingTypes$1.MAGNET_AVAILABILITY] = {
	        name: 'addClass',
	        options: {
	            className: 'available-magnet'
	        }
	    };
	defaultHighlighting[HighlightingTypes$1.ELEMENT_AVAILABILITY] = {
	        name: 'addClass',
	        options: {
	            className: 'available-cell'
	        }
	    };

	var defaultLayers = [{
	    name: LayersNames.BACK,
	}, {
	    name: LayersNames.CELLS,
	}, {
	    name: LayersNames.LABELS,
	}, {
	    name: LayersNames.FRONT
	}, {
	    name: LayersNames.TOOLS
	}];

	var Paper = View.extend({

	    className: 'paper',

	    options: {

	        width: 800,
	        height: 600,
	        origin: { x: 0, y: 0 }, // x,y coordinates in top-left corner
	        gridSize: 1,
	        // Whether or not to draw the grid lines on the paper's DOM element.
	        // e.g drawGrid: true, drawGrid: { color: 'red', thickness: 2 }
	        drawGrid: false,
	        // If not set, the size of the visual grid is the same as the `gridSize`.
	        drawGridSize: null,

	        // Whether or not to draw the background on the paper's DOM element.
	        // e.g. background: { color: 'lightblue', image: '/paper-background.png', repeat: 'flip-xy' }
	        background: false,

	        perpendicularLinks: false,
	        elementView: ElementView,
	        linkView: LinkView,
	        snapLabels: false, // false, true
	        snapLinks: false, // false, true, { radius: value }
	        snapLinksSelf: false, // false, true, { radius: value }

	        // Should the link labels be rendered into its own layer?
	        // `false` - the labels are part of the links
	        // `true` - the labels are appended to LayersName.LABELS
	        // [LayersName] - the labels are appended to the layer specified
	        labelsLayer: false,

	        // When set to FALSE, an element may not have more than 1 link with the same source and target element.
	        multiLinks: true,

	        // For adding custom guard logic.
	        guard: function(evt, view) {

	            // FALSE means the event isn't guarded.
	            return false;
	        },

	        highlighting: defaultHighlighting,

	        // Prevent the default context menu from being displayed.
	        preventContextMenu: true,

	        // Prevent the default action for blank:pointer<action>.
	        preventDefaultBlankAction: true,

	        // Prevent the default action for cell:pointer<action>.
	        preventDefaultViewAction: true,

	        // Restrict the translation of elements by given bounding box.
	        // Option accepts a boolean:
	        //  true - the translation is restricted to the paper area
	        //  false - no restrictions
	        // A method:
	        // restrictTranslate: function(elementView) {
	        //     var parentId = elementView.model.get('parent');
	        //     return parentId && this.model.getCell(parentId).getBBox();
	        // },
	        // Or a bounding box:
	        // restrictTranslate: { x: 10, y: 10, width: 790, height: 590 }
	        restrictTranslate: false,

	        // Marks all available magnets with 'available-magnet' class name and all available cells with
	        // 'available-cell' class name. Marks them when dragging a link is started and unmark
	        // when the dragging is stopped.
	        markAvailable: false,

	        // Defines what link model is added to the graph after an user clicks on an active magnet.
	        // Value could be the Backbone.model or a function returning the Backbone.model
	        // defaultLink: function(elementView, magnet) { return condition ? new customLink1() : new customLink2() }
	        defaultLink: new Link,

	        // A connector that is used by links with no connector defined on the model.
	        // e.g. { name: 'rounded', args: { radius: 5 }} or a function
	        defaultConnector: { name: 'normal' },

	        // A router that is used by links with no router defined on the model.
	        // e.g. { name: 'oneSide', args: { padding: 10 }} or a function
	        defaultRouter: { name: 'normal' },

	        defaultAnchor: { name: 'center' },

	        defaultLinkAnchor: { name: 'connectionRatio' },

	        defaultConnectionPoint: { name: 'bbox' },

	        /* CONNECTING */

	        connectionStrategy: null,

	        // Check whether to add a new link to the graph when user clicks on an a magnet.
	        validateMagnet: function(_cellView, magnet, _evt) {
	            return magnet.getAttribute('magnet') !== 'passive';
	        },

	        // Check whether to allow or disallow the link connection while an arrowhead end (source/target)
	        // being changed.
	        validateConnection: function(cellViewS, _magnetS, cellViewT, _magnetT, end, _linkView) {
	            return (end === 'target' ? cellViewT : cellViewS) instanceof ElementView;
	        },

	        /* EMBEDDING */

	        // Enables embedding. Re-parent the dragged element with elements under it and makes sure that
	        // all links and elements are visible taken the level of embedding into account.
	        embeddingMode: false,

	        // Check whether to allow or disallow the element embedding while an element being translated.
	        validateEmbedding: function(childView, parentView) {
	            // by default all elements can be in relation child-parent
	            return true;
	        },

	        // Check whether to allow or disallow an embedded element to be unembedded / to become a root.
	        validateUnembedding: function(childView) {
	            // by default all elements can become roots
	            return true;
	        },

	        // Determines the way how a cell finds a suitable parent when it's dragged over the paper.
	        // The cell with the highest z-index (visually on the top) will be chosen.
	        findParentBy: 'bbox', // 'bbox'|'center'|'origin'|'corner'|'topRight'|'bottomLeft'

	        // If enabled only the element on the very front is taken into account for the embedding.
	        // If disabled the elements under the dragged view are tested one by one
	        // (from front to back) until a valid parent found.
	        frontParentOnly: true,

	        // Interactive flags. See online docs for the complete list of interactive flags.
	        interactive: {
	            labelMove: false
	        },

	        // When set to true the links can be pinned to the paper.
	        // i.e. link source/target can be a point e.g. link.get('source') ==> { x: 100, y: 100 };
	        linkPinning: true,

	        // Custom validation after an interaction with a link ends.
	        // Recognizes a function. If `false` is returned, the link is disallowed (removed or reverted)
	        // (linkView, paper) => boolean
	        allowLink: null,

	        // Allowed number of mousemove events after which the pointerclick event will be still triggered.
	        clickThreshold: 0,

	        // Number of required mousemove events before the first pointermove event will be triggered.
	        moveThreshold: 0,

	        // Number of required mousemove events before a link is created out of the magnet.
	        // Or string `onleave` so the link is created when the pointer leaves the magnet
	        magnetThreshold: 0,

	        // Rendering Options

	        sorting: sortingTypes.EXACT,

	        frozen: false,

	        autoFreeze: false,

	        // no docs yet
	        onViewUpdate: function(view, flag, priority, opt, paper) {
	            // Do not update connected links when:
	            // 1. the view was just inserted (added to the graph and rendered)
	            // 2. the view was just mounted (added back to the paper by viewport function)
	            // 3. the change was marked as `isolate`.
	            // 4. the view model was just removed from the graph
	            if ((flag & (view.FLAG_INSERT | view.FLAG_REMOVE)) || opt.mounting || opt.isolate) { return; }
	            paper.requestConnectedLinksUpdate(view, priority, opt);
	        },

	        // no docs yet
	        onViewPostponed: function(view, flag, paper) {
	            return paper.forcePostponedViewUpdate(view, flag);
	        },

	        beforeRender: null, // function(opt, paper) { },

	        afterRender: null, // function(stats, opt, paper) {

	        viewport: null,

	        // Default namespaces

	        cellViewNamespace: null,

	        routerNamespace: null,

	        connectorNamespace: null,

	        highlighterNamespace: highlighters,

	        anchorNamespace: anchors,

	        linkAnchorNamespace: linkAnchors,

	        connectionPointNamespace: connectionPoints,

	        overflow: false
	    },

	    events: {
	        'dblclick': 'pointerdblclick',
	        'dbltap': 'pointerdblclick',
	        'contextmenu': 'contextmenu',
	        'mousedown': 'pointerdown',
	        'touchstart': 'pointerdown',
	        'mouseover': 'mouseover',
	        'mouseout': 'mouseout',
	        'mouseenter': 'mouseenter',
	        'mouseleave': 'mouseleave',
	        'wheel': 'mousewheel',
	        'mouseenter .joint-cell': 'mouseenter',
	        'mouseleave .joint-cell': 'mouseleave',
	        'mouseenter .joint-tools': 'mouseenter',
	        'mouseleave .joint-tools': 'mouseleave',
	        'dblclick .joint-cell [magnet]': 'magnetpointerdblclick',
	        'contextmenu .joint-cell [magnet]': 'magnetcontextmenu',
	        'mousedown .joint-link .label': 'onlabel', // interaction with link label
	        'touchstart .joint-link .label': 'onlabel',
	        'dragstart .joint-cell image': 'onImageDragStart' // firefox fix
	    },

	    documentEvents: {
	        'mousemove': 'pointermove',
	        'touchmove': 'pointermove',
	        'mouseup': 'pointerup',
	        'touchend': 'pointerup',
	        'touchcancel': 'pointerup'
	    },

	    svg: null,
	    viewport: null,
	    defs: null,
	    tools: null,
	    $background: null,
	    layers: null,
	    $grid: null,
	    $document: null,

	    // For storing the current transformation matrix (CTM) of the paper's viewport.
	    _viewportMatrix: null,
	    // For verifying whether the CTM is up-to-date. The viewport transform attribute
	    // could have been manipulated directly.
	    _viewportTransformString: null,
	    // Updates data (priorities, unmounted views etc.)
	    _updates: null,
	    // Paper Layers
	    _layers: null,

	    SORT_DELAYING_BATCHES: ['add', 'to-front', 'to-back'],
	    UPDATE_DELAYING_BATCHES: ['translate'],
	    // If you interact with these elements,
	    // the default interaction such as `element move` is prevented.
	    FORM_CONTROL_TAG_NAMES: ['TEXTAREA', 'INPUT', 'BUTTON', 'SELECT', 'OPTION'] ,
	    // If you interact with these elements, the events are not propagated to the paper
	    // i.e. paper events such as `element:pointerdown` are not triggered.
	    GUARDED_TAG_NAMES: [
	        // Guard <select> for consistency. When you click on it:
	        // Chrome: triggers `pointerdown`, `pointerup`, `pointerclick` to open
	        // Firefox: triggers `pointerdown` on open, `pointerup` (and `pointerclick` only if you haven't moved).
	        //          on close. However, if you open and then close by clicking elsewhere on the page,
	        //           no other event is triggered.
	        // Safari: when you open it, it triggers `pointerdown`. That's it.
	        'SELECT' ],
	    MIN_SCALE: 1e-6,

	    init: function() {

	        var ref = this;
	        var options = ref.options;
	        var el = ref.el;
	        if (!options.cellViewNamespace) {
	            /* eslint-disable no-undef */
	            options.cellViewNamespace = typeof joint !== 'undefined' && has$2(joint, 'shapes') ? joint.shapes : null;
	            /* eslint-enable no-undef */
	        }

	        var model = this.model = options.model || new Graph;

	        // Layers (SVGGroups)
	        this._layers = {};

	        this.setGrid(options.drawGrid);
	        this.cloneOptions();
	        this.render();
	        this._setDimensions();
	        this.startListening();

	        // Hash of all cell views.
	        this._views = {};

	        // Mouse wheel events buffer
	        this._mw_evt_buffer = {
	            event: null,
	            deltas: [],
	        };

	        // Reference to the paper owner document
	        this.$document = $(el.ownerDocument);
	        // Render existing cells in the graph
	        this.resetViews(model.attributes.cells.models);
	        // Start the Rendering Loop
	        if (!this.isFrozen() && this.isAsync()) { this.updateViewsAsync(); }
	    },

	    _resetUpdates: function() {
	        return this._updates = {
	            id: null,
	            priorities: [{}, {}, {}],
	            unmountedCids: [],
	            mountedCids: [],
	            unmounted: {},
	            mounted: {},
	            count: 0,
	            keyFrozen: false,
	            freezeKey: null,
	            sort: false,
	            disabled: false,
	            idle: false
	        };
	    },

	    startListening: function() {
	        var model = this.model;
	        this.listenTo(model, 'add', this.onCellAdded)
	            .listenTo(model, 'remove', this.onCellRemoved)
	            .listenTo(model, 'change', this.onCellChange)
	            .listenTo(model, 'reset', this.onGraphReset)
	            .listenTo(model, 'sort', this.onGraphSort)
	            .listenTo(model, 'batch:stop', this.onGraphBatchStop);
	        this.on('cell:highlight', this.onCellHighlight)
	            .on('cell:unhighlight', this.onCellUnhighlight)
	            .on('scale translate', this.update);
	    },

	    onCellAdded: function(cell, _, opt) {
	        var position = opt.position;
	        if (this.isAsync() || !isNumber(position)) {
	            this.renderView(cell, opt);
	        } else {
	            if (opt.maxPosition === position) { this.freeze({ key: 'addCells' }); }
	            this.renderView(cell, opt);
	            if (position === 0) { this.unfreeze({ key: 'addCells' }); }
	        }
	    },

	    onCellRemoved: function(cell, _, opt) {
	        var view = this.findViewByModel(cell);
	        if (view) { this.requestViewUpdate(view, view.FLAG_REMOVE, view.UPDATE_PRIORITY, opt); }
	    },

	    onCellChange: function(cell, opt) {
	        if (cell === this.model.attributes.cells) { return; }
	        if (cell.hasChanged('z') && this.options.sorting === sortingTypes.APPROX) {
	            var view = this.findViewByModel(cell);
	            if (view) { this.requestViewUpdate(view, view.FLAG_INSERT, view.UPDATE_PRIORITY, opt); }
	        }
	    },

	    onGraphReset: function(collection, opt) {
	        this.resetLayers();
	        this.resetViews(collection.models, opt);
	    },

	    onGraphSort: function() {
	        if (this.model.hasActiveBatch(this.SORT_DELAYING_BATCHES)) { return; }
	        this.sortViews();
	    },

	    onGraphBatchStop: function(data) {
	        if (this.isFrozen()) { return; }
	        var name = data && data.batchName;
	        var graph = this.model;
	        if (!this.isAsync()) {
	            var updateDelayingBatches = this.UPDATE_DELAYING_BATCHES;
	            if (updateDelayingBatches.includes(name) && !graph.hasActiveBatch(updateDelayingBatches)) {
	                this.updateViews(data);
	            }
	        }
	        var sortDelayingBatches = this.SORT_DELAYING_BATCHES;
	        if (sortDelayingBatches.includes(name) && !graph.hasActiveBatch(sortDelayingBatches)) {
	            this.sortViews();
	        }
	    },

	    cloneOptions: function() {

	        var ref = this;
	        var options = ref.options;
	        var defaultConnector = options.defaultConnector;
	        var defaultRouter = options.defaultRouter;
	        var defaultConnectionPoint = options.defaultConnectionPoint;
	        var defaultAnchor = options.defaultAnchor;
	        var defaultLinkAnchor = options.defaultLinkAnchor;
	        var origin = options.origin;
	        var highlighting = options.highlighting;
	        var cellViewNamespace = options.cellViewNamespace;
	        var interactive = options.interactive;

	        // Default cellView namespace for ES5
	        /* eslint-disable no-undef */
	        if (!cellViewNamespace && typeof joint !== 'undefined' && has$2(joint, 'shapes')) {
	            options.cellViewNamespace = joint.shapes;
	        }
	        /* eslint-enable no-undef */

	        // Here if a function was provided, we can not clone it, as this would result in loosing the function.
	        // If the default is used, the cloning is necessary in order to prevent modifying the options on prototype.
	        if (!isFunction(defaultConnector)) {
	            options.defaultConnector = cloneDeep(defaultConnector);
	        }
	        if (!isFunction(defaultRouter)) {
	            options.defaultRouter = cloneDeep(defaultRouter);
	        }
	        if (!isFunction(defaultConnectionPoint)) {
	            options.defaultConnectionPoint = cloneDeep(defaultConnectionPoint);
	        }
	        if (!isFunction(defaultAnchor)) {
	            options.defaultAnchor = cloneDeep(defaultAnchor);
	        }
	        if (!isFunction(defaultLinkAnchor)) {
	            options.defaultLinkAnchor = cloneDeep(defaultLinkAnchor);
	        }
	        if (isPlainObject(interactive)) {
	            options.interactive = assign({}, interactive);
	        }
	        if (isPlainObject(highlighting)) {
	            // Return the default highlighting options into the user specified options.
	            options.highlighting = defaultsDeep({}, highlighting, defaultHighlighting);
	        }
	        options.origin = assign({}, origin);
	    },

	    children: function() {
	        var ns = V.namespace;
	        return [{
	            namespaceURI: ns.xhtml,
	            tagName: 'div',
	            className: addClassNamePrefix('paper-background'),
	            selector: 'background'
	        }, {
	            namespaceURI: ns.xhtml,
	            tagName: 'div',
	            className: addClassNamePrefix('paper-grid'),
	            selector: 'grid'
	        }, {
	            namespaceURI: ns.svg,
	            tagName: 'svg',
	            attributes: {
	                'width': '100%',
	                'height': '100%',
	                'xmlns:xlink': ns.xlink
	            },
	            selector: 'svg',
	            children: [{
	                // Append `<defs>` element to the SVG document. This is useful for filters and gradients.
	                // It's desired to have the defs defined before the viewport (e.g. to make a PDF document pick up defs properly).
	                tagName: 'defs',
	                selector: 'defs'
	            }, {
	                tagName: 'g',
	                className: addClassNamePrefix('layers'),
	                selector: 'layers'
	            }]
	        }];
	    },

	    hasLayerView: function hasLayerView(layerName) {
	        return  (layerName in this._layers);
	    },

	    getLayerView: function getLayerView(layerName) {
	        var ref = this;
	        var _layers = ref._layers;
	        if (layerName in _layers) { return _layers[layerName]; }
	        throw new Error(("dia.Paper: Unknown layer \"" + layerName + "\""));
	    },

	    getLayerNode: function getLayerNode(layerName) {
	        return this.getLayerView(layerName).el;
	    },

	    render: function() {

	        this.renderChildren();
	        var ref = this;
	        var childNodes = ref.childNodes;
	        var options = ref.options;
	        var svg = childNodes.svg;
	        var defs = childNodes.defs;
	        var layers = childNodes.layers;
	        var background = childNodes.background;
	        var grid = childNodes.grid;

	        svg.style.overflow = options.overflow ? 'visible' : 'hidden';

	        this.svg = svg;
	        this.defs = defs;
	        this.layers = layers;
	        this.$background = $(background);
	        this.$grid = $(grid);

	        this.renderLayers();

	        V.ensureId(svg);

	        if (options.background) {
	            this.drawBackground(options.background);
	        }

	        if (options.drawGrid) {
	            this.drawGrid();
	        }

	        return this;
	    },

	    renderLayers: function(layers) {
	        var this$1 = this;
	        if ( layers === void 0 ) layers = defaultLayers;

	        this.removeLayers();
	        // TODO: Layers to be read from the graph `layers` attribute
	        layers.forEach(function (ref) {
	            var name = ref.name;
	            var sorted = ref.sorted;

	            var layerView = new PaperLayer({ name: name });
	            this$1.layers.appendChild(layerView.el);
	            this$1._layers[name] = layerView;
	        });
	        // Throws an exception if doesn't exist
	        var cellsLayerView = this.getLayerView(LayersNames.CELLS);
	        var toolsLayerView = this.getLayerView(LayersNames.TOOLS);
	        var labelsLayerView = this.getLayerView(LayersNames.LABELS);
	        // backwards compatibility
	        this.tools = toolsLayerView.el;
	        this.cells = this.viewport = cellsLayerView.el;
	        // user-select: none;
	        cellsLayerView.vel.addClass(addClassNamePrefix('viewport'));
	        labelsLayerView.vel.addClass(addClassNamePrefix('viewport'));
	    },

	    removeLayers: function() {
	        var ref = this;
	        var _layers = ref._layers;
	        Object.keys(_layers).forEach(function (name) {
	            _layers[name].remove();
	            delete _layers[name];
	        });
	    },

	    resetLayers: function() {
	        var ref = this;
	        var _layers = ref._layers;
	        Object.keys(_layers).forEach(function (name) {
	            _layers[name].removePivots();
	        });
	    },

	    update: function() {

	        if (this.options.drawGrid) {
	            this.drawGrid();
	        }

	        if (this._background) {
	            this.updateBackgroundImage(this._background);
	        }

	        return this;
	    },

	    matrix: function(ctm) {

	        var viewport = this.layers;

	        // Getter:
	        if (ctm === undefined) {

	            var transformString = viewport.getAttribute('transform');

	            if ((this._viewportTransformString || null) === transformString) {
	                // It's ok to return the cached matrix. The transform attribute has not changed since
	                // the matrix was stored.
	                ctm = this._viewportMatrix;
	            } else {
	                // The viewport transform attribute has changed. Measure the matrix and cache again.
	                ctm = viewport.getCTM();
	                this._viewportMatrix = ctm;
	                this._viewportTransformString = transformString;
	            }

	            // Clone the cached current transformation matrix.
	            // If no matrix previously stored the identity matrix is returned.
	            return V.createSVGMatrix(ctm);
	        }

	        // Setter:
	        ctm = V.createSVGMatrix(ctm);
	        var ctmString = V.matrixToTransformString(ctm);
	        viewport.setAttribute('transform', ctmString);

	        this._viewportMatrix = ctm;
	        this._viewportTransformString = viewport.getAttribute('transform');

	        return this;
	    },

	    clientMatrix: function() {

	        return V.createSVGMatrix(this.cells.getScreenCTM());
	    },

	    requestConnectedLinksUpdate: function(view, priority, opt) {
	        if (view instanceof CellView) {
	            var model = view.model;
	            var links = this.model.getConnectedLinks(model);
	            for (var j = 0, n = links.length; j < n; j++) {
	                var link = links[j];
	                var linkView = this.findViewByModel(link);
	                if (!linkView) { continue; }
	                var flagLabels = ['UPDATE'];
	                if (link.getTargetCell() === model) { flagLabels.push('TARGET'); }
	                if (link.getSourceCell() === model) { flagLabels.push('SOURCE'); }
	                var nextPriority = Math.max(priority + 1, linkView.UPDATE_PRIORITY);
	                this.scheduleViewUpdate(linkView, linkView.getFlag(flagLabels), nextPriority, opt);
	            }
	        }
	    },

	    forcePostponedViewUpdate: function(view, flag) {
	        if (!view || !(view instanceof CellView)) { return false; }
	        var model = view.model;
	        if (model.isElement()) { return false; }
	        if ((flag & view.getFlag(['SOURCE', 'TARGET'])) === 0) {
	            var dumpOptions = { silent: true };
	            // LinkView is waiting for the target or the source cellView to be rendered
	            // This can happen when the cells are not in the viewport.
	            var sourceFlag = 0;
	            var sourceView = this.findViewByModel(model.getSourceCell());
	            if (sourceView && !this.isViewMounted(sourceView)) {
	                sourceFlag = this.dumpView(sourceView, dumpOptions);
	                view.updateEndMagnet('source');
	            }
	            var targetFlag = 0;
	            var targetView = this.findViewByModel(model.getTargetCell());
	            if (targetView && !this.isViewMounted(targetView)) {
	                targetFlag = this.dumpView(targetView, dumpOptions);
	                view.updateEndMagnet('target');
	            }
	            if (sourceFlag === 0 && targetFlag === 0) {
	                // If leftover flag is 0, all view updates were done.
	                return !this.dumpView(view, dumpOptions);
	            }
	        }
	        return false;
	    },

	    requestViewUpdate: function(view, flag, priority, opt) {
	        opt || (opt = {});
	        this.scheduleViewUpdate(view, flag, priority, opt);
	        var isAsync = this.isAsync();
	        if (this.isFrozen() || (isAsync && opt.async !== false)) { return; }
	        if (this.model.hasActiveBatch(this.UPDATE_DELAYING_BATCHES)) { return; }
	        var stats = this.updateViews(opt);
	        if (isAsync) { this.notifyAfterRender(stats, opt); }
	    },

	    scheduleViewUpdate: function(view, type, priority, opt) {
	        var ref = this;
	        var updates = ref._updates;
	        var options = ref.options;
	        if (updates.idle) {
	            if (options.autoFreeze) {
	                updates.idle = false;
	                this.unfreeze();
	            }
	        }
	        var FLAG_REMOVE = view.FLAG_REMOVE;
	        var FLAG_INSERT = view.FLAG_INSERT;
	        var UPDATE_PRIORITY = view.UPDATE_PRIORITY;
	        var cid = view.cid;
	        var priorityUpdates = updates.priorities[priority];
	        if (!priorityUpdates) { priorityUpdates = updates.priorities[priority] = {}; }
	        // Move higher priority updates to this priority
	        if (priority > UPDATE_PRIORITY) {
	            // Not the default priority for this view. It's most likely a link view
	            // connected to another link view, which triggered the update.
	            // TODO: If there is an update scheduled with a lower priority already, we should
	            // change the requested priority to the lowest one. Does not seem to be critical
	            // right now, as it "only" results in multiple updates on the same view.
	            for (var i = priority - 1; i >= UPDATE_PRIORITY; i--) {
	                var prevPriorityUpdates = updates.priorities[i];
	                if (!prevPriorityUpdates || !(cid in prevPriorityUpdates)) { continue; }
	                priorityUpdates[cid] |= prevPriorityUpdates[cid];
	                delete prevPriorityUpdates[cid];
	            }
	        }
	        var currentType = priorityUpdates[cid] || 0;
	        // Prevent cycling
	        if ((currentType & type) === type) { return; }
	        if (!currentType) { updates.count++; }
	        if (type & FLAG_REMOVE && currentType & FLAG_INSERT) {
	            // When a view is removed we need to remove the insert flag as this is a reinsert
	            priorityUpdates[cid] ^= FLAG_INSERT;
	        } else if (type & FLAG_INSERT && currentType & FLAG_REMOVE) {
	            // When a view is added we need to remove the remove flag as this is view was previously removed
	            priorityUpdates[cid] ^= FLAG_REMOVE;
	        }
	        priorityUpdates[cid] |= type;
	        var viewUpdateFn = options.onViewUpdate;
	        if (typeof viewUpdateFn === 'function') { viewUpdateFn.call(this, view, type, priority, opt || {}, this); }
	    },

	    dumpViewUpdate: function(view) {
	        if (!view) { return 0; }
	        var updates = this._updates;
	        var cid = view.cid;
	        var priorityUpdates = updates.priorities[view.UPDATE_PRIORITY];
	        var flag = this.registerMountedView(view) | priorityUpdates[cid];
	        delete priorityUpdates[cid];
	        return flag;
	    },

	    dumpView: function(view, opt) {
	        if ( opt === void 0 ) opt = {};

	        var flag = this.dumpViewUpdate(view);
	        if (!flag) { return 0; }
	        var shouldNotify = !opt.silent;
	        if (shouldNotify) { this.notifyBeforeRender(opt); }
	        var leftover = this.updateView(view, flag, opt);
	        if (shouldNotify) {
	            var stats = { updated: 1, priority: view.UPDATE_PRIORITY };
	            this.notifyAfterRender(stats, opt);
	        }
	        return leftover;
	    },

	    updateView: function(view, flag, opt) {
	        if (!view) { return 0; }
	        var FLAG_REMOVE = view.FLAG_REMOVE;
	        var FLAG_INSERT = view.FLAG_INSERT;
	        var FLAG_INIT = view.FLAG_INIT;
	        var model = view.model;
	        if (view instanceof CellView) {
	            if (flag & FLAG_REMOVE) {
	                this.removeView(model);
	                return 0;
	            }
	            if (flag & FLAG_INSERT) {
	                var isInitialInsert = !!(flag & FLAG_INIT);
	                if (isInitialInsert) {
	                    flag ^= FLAG_INIT;
	                }
	                this.insertView(view, isInitialInsert);
	                flag ^= FLAG_INSERT;
	            }
	        }
	        if (!flag) { return 0; }
	        return view.confirmUpdate(flag, opt || {});
	    },

	    requireView: function(model, opt) {
	        var view = this.findViewByModel(model);
	        if (!view) { return null; }
	        this.dumpView(view, opt);
	        return view;
	    },

	    registerUnmountedView: function(view) {
	        var cid = view.cid;
	        var updates = this._updates;
	        if (cid in updates.unmounted) { return 0; }
	        var flag = updates.unmounted[cid] |= view.FLAG_INSERT;
	        updates.unmountedCids.push(cid);
	        delete updates.mounted[cid];
	        return flag;
	    },

	    registerMountedView: function(view) {
	        var cid = view.cid;
	        var updates = this._updates;
	        if (cid in updates.mounted) { return 0; }
	        updates.mounted[cid] = true;
	        updates.mountedCids.push(cid);
	        var flag = updates.unmounted[cid] || 0;
	        delete updates.unmounted[cid];
	        return flag;
	    },

	    isViewMounted: function(view) {
	        if (!view) { return false; }
	        var cid = view.cid;
	        var updates = this._updates;
	        return (cid in updates.mounted);
	    },

	    dumpViews: function(opt) {
	        var passingOpt = defaults({}, opt, { viewport: null });
	        this.checkViewport(passingOpt);
	        this.updateViews(passingOpt);
	    },

	    // Synchronous views update
	    updateViews: function(opt) {
	        this.notifyBeforeRender(opt);
	        var batchStats;
	        var updateCount = 0;
	        var batchCount = 0;
	        var priority = MIN_PRIORITY;
	        do {
	            batchCount++;
	            batchStats = this.updateViewsBatch(opt);
	            updateCount += batchStats.updated;
	            priority = Math.min(batchStats.priority, priority);
	        } while (!batchStats.empty);
	        var stats = { updated: updateCount, batches: batchCount, priority: priority };
	        this.notifyAfterRender(stats, opt);
	        return stats;
	    },

	    hasScheduledUpdates: function() {
	        var priorities = this._updates.priorities;
	        var priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
	        var i = priorityIndexes.length;
	        while (i > 0 && i--) {
	            // a faster way how to check if an object is empty
	            for (var _key in priorities[priorityIndexes[i]]) { return true; }
	        }
	        return false;
	    },

	    updateViewsAsync: function(opt, data) {
	        opt || (opt = {});
	        data || (data = { processed: 0, priority: MIN_PRIORITY });
	        var ref = this;
	        var updates = ref._updates;
	        var options = ref.options;
	        var id = updates.id;
	        if (id) {
	            cancelFrame(id);
	            if (data.processed === 0 && this.hasScheduledUpdates()) {
	                this.notifyBeforeRender(opt);
	            }
	            var stats = this.updateViewsBatch(opt);
	            var passingOpt = defaults({}, opt, {
	                mountBatchSize: MOUNT_BATCH_SIZE - stats.mounted,
	                unmountBatchSize: MOUNT_BATCH_SIZE - stats.unmounted
	            });
	            var checkStats = this.checkViewport(passingOpt);
	            var unmountCount = checkStats.unmounted;
	            var mountCount = checkStats.mounted;
	            var processed = data.processed;
	            var total = updates.count;
	            if (stats.updated > 0) {
	                // Some updates have been just processed
	                processed += stats.updated + stats.unmounted;
	                stats.processed = processed;
	                data.priority = Math.min(stats.priority, data.priority);
	                if (stats.empty && mountCount === 0) {
	                    stats.unmounted += unmountCount;
	                    stats.mounted += mountCount;
	                    stats.priority = data.priority;
	                    this.notifyAfterRender(stats, opt);
	                    data.processed = 0;
	                    data.priority = MIN_PRIORITY;
	                    updates.count = 0;
	                } else {
	                    data.processed = processed;
	                }
	            } else {
	                if (!updates.idle) {
	                    if (options.autoFreeze) {
	                        this.freeze();
	                        updates.idle = true;
	                        this.trigger('render:idle', opt);
	                    }
	                }
	            }
	            // Progress callback
	            var progressFn = opt.progress;
	            if (total && typeof progressFn === 'function') {
	                progressFn.call(this, stats.empty, processed, total, stats, this);
	            }
	            // The current frame could have been canceled in a callback
	            if (updates.id !== id) { return; }
	        }
	        if (updates.disabled) {
	            throw new Error('dia.Paper: can not unfreeze the paper after it was removed');
	        }
	        updates.id = nextFrame(this.updateViewsAsync, this, opt, data);
	    },

	    notifyBeforeRender: function(opt) {
	        if ( opt === void 0 ) opt = {};

	        var beforeFn = opt.beforeRender;
	        if (typeof beforeFn !== 'function') {
	            beforeFn = this.options.beforeRender;
	            if (typeof beforeFn !== 'function') { return; }
	        }
	        beforeFn.call(this, opt, this);
	    },

	    notifyAfterRender: function(stats, opt) {
	        if ( opt === void 0 ) opt = {};

	        var afterFn = opt.afterRender;
	        if (typeof afterFn !== 'function') {
	            afterFn = this.options.afterRender;
	        }
	        if (typeof afterFn === 'function') {
	            afterFn.call(this, stats, opt, this);
	        }
	        this.trigger('render:done', stats, opt);
	    },

	    updateViewsBatch: function(opt) {
	        opt || (opt = {});
	        var batchSize = opt.batchSize || UPDATE_BATCH_SIZE;
	        var updates = this._updates;
	        var updateCount = 0;
	        var postponeCount = 0;
	        var unmountCount = 0;
	        var mountCount = 0;
	        var maxPriority = MIN_PRIORITY;
	        var empty = true;
	        var options = this.options;
	        var priorities = updates.priorities;
	        var viewportFn = 'viewport' in opt ? opt.viewport : options.viewport;
	        if (typeof viewportFn !== 'function') { viewportFn = null; }
	        var postponeViewFn = options.onViewPostponed;
	        if (typeof postponeViewFn !== 'function') { postponeViewFn = null; }
	        var priorityIndexes = Object.keys(priorities); // convert priorities to a dense array
	        main: for (var i = 0, n = priorityIndexes.length; i < n; i++) {
	            var priority = +priorityIndexes[i];
	            var priorityUpdates = priorities[priority];
	            for (var cid in priorityUpdates) {
	                if (updateCount >= batchSize) {
	                    empty = false;
	                    break main;
	                }
	                var view = views[cid];
	                if (!view) {
	                    // This should not occur
	                    delete priorityUpdates[cid];
	                    continue;
	                }
	                var currentFlag = priorityUpdates[cid];
	                if ((currentFlag & view.FLAG_REMOVE) === 0) {
	                    // We should never check a view for viewport if we are about to remove the view
	                    var isDetached = cid in updates.unmounted;
	                    if (view.DETACHABLE && viewportFn && !viewportFn.call(this, view, !isDetached, this)) {
	                        // Unmount View
	                        if (!isDetached) {
	                            this.registerUnmountedView(view);
	                            this.detachView(view);
	                        }
	                        updates.unmounted[cid] |= currentFlag;
	                        delete priorityUpdates[cid];
	                        unmountCount++;
	                        continue;
	                    }
	                    // Mount View
	                    if (isDetached) {
	                        currentFlag |= view.FLAG_INSERT;
	                        mountCount++;
	                    }
	                    currentFlag |= this.registerMountedView(view);
	                }
	                var leftoverFlag = this.updateView(view, currentFlag, opt);
	                if (leftoverFlag > 0) {
	                    // View update has not finished completely
	                    priorityUpdates[cid] = leftoverFlag;
	                    if (!postponeViewFn || !postponeViewFn.call(this, view, leftoverFlag, this) || priorityUpdates[cid]) {
	                        postponeCount++;
	                        empty = false;
	                        continue;
	                    }
	                }
	                if (maxPriority > priority) { maxPriority = priority; }
	                updateCount++;
	                delete priorityUpdates[cid];
	            }
	        }
	        return {
	            priority: maxPriority,
	            updated: updateCount,
	            postponed: postponeCount,
	            unmounted: unmountCount,
	            mounted: mountCount,
	            empty: empty
	        };
	    },

	    getUnmountedViews: function() {
	        var updates = this._updates;
	        var unmountedCids = Object.keys(updates.unmounted);
	        var n = unmountedCids.length;
	        var unmountedViews = new Array(n);
	        for (var i = 0; i < n; i++) {
	            unmountedViews[i] = views[unmountedCids[i]];
	        }
	        return unmountedViews;
	    },

	    getMountedViews: function() {
	        var updates = this._updates;
	        var mountedCids = Object.keys(updates.mounted);
	        var n = mountedCids.length;
	        var mountedViews = new Array(n);
	        for (var i = 0; i < n; i++) {
	            mountedViews[i] = views[mountedCids[i]];
	        }
	        return mountedViews;
	    },

	    checkUnmountedViews: function(viewportFn, opt) {
	        opt || (opt  = {});
	        var mountCount = 0;
	        if (typeof viewportFn !== 'function') { viewportFn = null; }
	        var batchSize = 'mountBatchSize' in opt ? opt.mountBatchSize : Infinity;
	        var updates = this._updates;
	        var unmountedCids = updates.unmountedCids;
	        var unmounted = updates.unmounted;
	        for (var i = 0, n = Math.min(unmountedCids.length, batchSize); i < n; i++) {
	            var cid = unmountedCids[i];
	            if (!(cid in unmounted)) { continue; }
	            var view = views[cid];
	            if (!view) { continue; }
	            if (view.DETACHABLE && viewportFn && !viewportFn.call(this, view, false, this)) {
	                // Push at the end of all unmounted ids, so this can be check later again
	                unmountedCids.push(cid);
	                continue;
	            }
	            mountCount++;
	            var flag = this.registerMountedView(view);
	            if (flag) { this.scheduleViewUpdate(view, flag, view.UPDATE_PRIORITY, { mounting: true }); }
	        }
	        // Get rid of views, that have been mounted
	        unmountedCids.splice(0, i);
	        return mountCount;
	    },

	    checkMountedViews: function(viewportFn, opt) {
	        opt || (opt = {});
	        var unmountCount = 0;
	        if (typeof viewportFn !== 'function') { return unmountCount; }
	        var batchSize = 'unmountBatchSize' in opt ? opt.unmountBatchSize : Infinity;
	        var updates = this._updates;
	        var mountedCids = updates.mountedCids;
	        var mounted = updates.mounted;
	        for (var i = 0, n = Math.min(mountedCids.length, batchSize); i < n; i++) {
	            var cid = mountedCids[i];
	            if (!(cid in mounted)) { continue; }
	            var view = views[cid];
	            if (!view) { continue; }
	            if (!view.DETACHABLE || viewportFn.call(this, view, true, this)) {
	                // Push at the end of all mounted ids, so this can be check later again
	                mountedCids.push(cid);
	                continue;
	            }
	            unmountCount++;
	            var flag = this.registerUnmountedView(view);
	            if (flag) { this.detachView(view); }
	        }
	        // Get rid of views, that have been unmounted
	        mountedCids.splice(0, i);
	        return unmountCount;
	    },

	    checkViewport: function(opt) {
	        var passingOpt = defaults({}, opt, {
	            mountBatchSize: Infinity,
	            unmountBatchSize: Infinity
	        });
	        var viewportFn = 'viewport' in passingOpt ? passingOpt.viewport : this.options.viewport;
	        var unmountedCount = this.checkMountedViews(viewportFn, passingOpt);
	        if (unmountedCount > 0) {
	            // Do not check views, that have been just unmounted and pushed at the end of the cids array
	            var unmountedCids = this._updates.unmountedCids;
	            passingOpt.mountBatchSize = Math.min(unmountedCids.length - unmountedCount, passingOpt.mountBatchSize);
	        }
	        var mountedCount = this.checkUnmountedViews(viewportFn, passingOpt);
	        return {
	            mounted: mountedCount,
	            unmounted: unmountedCount
	        };
	    },

	    freeze: function(opt) {
	        opt || (opt = {});
	        var updates = this._updates;
	        var key = opt.key;
	        var isFrozen = this.options.frozen;
	        var freezeKey = updates.freezeKey;
	        if (key && key !== freezeKey)  {
	            // key passed, but the paper is already freezed with another key
	            if (isFrozen && freezeKey) { return; }
	            updates.freezeKey = key;
	            updates.keyFrozen = isFrozen;
	        }
	        this.options.frozen = true;
	        var id = updates.id;
	        updates.id = null;
	        if (this.isAsync() && id) { cancelFrame(id); }
	    },

	    unfreeze: function(opt) {
	        opt || (opt = {});
	        var updates = this._updates;
	        var key = opt.key;
	        var freezeKey = updates.freezeKey;
	        // key passed, but the paper is already freezed with another key
	        if (key && freezeKey && key !== freezeKey) { return; }
	        updates.freezeKey = null;
	        // key passed, but the paper is already freezed
	        if (key && key === freezeKey && updates.keyFrozen) { return; }
	        if (this.isAsync()) {
	            this.freeze();
	            this.updateViewsAsync(opt);
	        } else {
	            this.updateViews(opt);
	        }
	        this.options.frozen = updates.keyFrozen = false;
	        if (updates.sort) {
	            this.sortViews();
	            updates.sort = false;
	        }
	    },

	    isAsync: function() {
	        return !!this.options.async;
	    },

	    isFrozen: function() {
	        return !!this.options.frozen;
	    },

	    isExactSorting: function() {
	        return this.options.sorting === sortingTypes.EXACT;
	    },

	    onRemove: function() {

	        this.freeze();
	        this._updates.disabled = true;
	        //clean up all DOM elements/views to prevent memory leaks
	        this.removeLayers();
	        this.removeViews();
	    },

	    getComputedSize: function() {

	        var options = this.options;
	        var w = options.width;
	        var h = options.height;
	        if (!isNumber(w)) { w = this.el.clientWidth; }
	        if (!isNumber(h)) { h = this.el.clientHeight; }
	        return { width: w, height: h };
	    },

	    setDimensions: function(width, height) {
	        var ref = this;
	        var options = ref.options;
	        var currentWidth = options.width;
	        var currentHeight = options.height;
	        var w = (width === undefined) ? currentWidth : width;
	        var h = (height === undefined) ? currentHeight : height;
	        if (currentWidth === w && currentHeight === h) { return; }
	        options.width = w;
	        options.height = h;
	        this._setDimensions();
	        var computedSize = this.getComputedSize();
	        this.trigger('resize', computedSize.width, computedSize.height);
	    },

	    _setDimensions: function() {
	        var ref = this;
	        var options = ref.options;
	        var w = options.width;
	        var h = options.height;
	        if (isNumber(w)) { w = Math.round(w); }
	        if (isNumber(h)) { h = Math.round(h); }
	        this.$el.css({
	            width: (w === null) ? '' : w,
	            height: (h === null) ? '' : h
	        });
	    },

	    setOrigin: function(ox, oy) {
	        return this.translate(ox || 0, oy || 0);
	    },

	    // Expand/shrink the paper to fit the content.
	    // Alternatively signature function(opt)
	    fitToContent: function(gridWidth, gridHeight, padding, opt) {

	        if (isObject$1(gridWidth)) {
	            // first parameter is an option object
	            opt = gridWidth;
	        } else {
	            // Support for a deprecated signature
	            opt = assign({ gridWidth: gridWidth, gridHeight: gridHeight, padding: padding }, opt);
	        }

	        var ref = this.getFitToContentArea(opt);
	        var x = ref.x;
	        var y = ref.y;
	        var width = ref.width;
	        var height = ref.height;
	        var ref$1 = this.scale();
	        var sx = ref$1.sx;
	        var sy = ref$1.sy;

	        this.setOrigin(-x * sx, -y * sy);
	        this.setDimensions(width * sx, height * sy);

	        return new Rect(x, y, width, height);
	    },

	    getFitToContentArea: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        // Calculate the paper size to accommodate all the graph's elements.

	        var gridWidth = opt.gridWidth || 1;
	        var gridHeight = opt.gridHeight || 1;
	        var padding = normalizeSides(opt.padding || 0);

	        var minWidth = Math.max(opt.minWidth || 0, gridWidth);
	        var minHeight = Math.max(opt.minHeight || 0, gridHeight);
	        var maxWidth = opt.maxWidth || Number.MAX_VALUE;
	        var maxHeight = opt.maxHeight || Number.MAX_VALUE;
	        var newOrigin = opt.allowNewOrigin;

	        var area = ('contentArea' in opt) ? new Rect(opt.contentArea) : this.getContentArea(opt);
	        var ref = this.scale();
	        var sx = ref.sx;
	        var sy = ref.sy;
	        area.x *= sx;
	        area.y *= sy;
	        area.width *= sx;
	        area.height *= sy;

	        var calcWidth = Math.ceil((area.width + area.x) / gridWidth);
	        var calcHeight = Math.ceil((area.height + area.y) / gridHeight);
	        if (!opt.allowNegativeBottomRight) {
	            calcWidth = Math.max(calcWidth, 1);
	            calcHeight = Math.max(calcHeight, 1);
	        }
	        calcWidth *= gridWidth;
	        calcHeight *= gridHeight;

	        var tx = 0;
	        if ((newOrigin === 'negative' && area.x < 0) || (newOrigin === 'positive' && area.x >= 0) || newOrigin === 'any') {
	            tx = Math.ceil(-area.x / gridWidth) * gridWidth;
	            tx += padding.left;
	            calcWidth += tx;
	        }

	        var ty = 0;
	        if ((newOrigin === 'negative' && area.y < 0) || (newOrigin === 'positive' && area.y >= 0) || newOrigin === 'any') {
	            ty = Math.ceil(-area.y / gridHeight) * gridHeight;
	            ty += padding.top;
	            calcHeight += ty;
	        }

	        calcWidth += padding.right;
	        calcHeight += padding.bottom;

	        // Make sure the resulting width and height are greater than minimum.
	        calcWidth = Math.max(calcWidth, minWidth);
	        calcHeight = Math.max(calcHeight, minHeight);

	        // Make sure the resulting width and height are lesser than maximum.
	        calcWidth = Math.min(calcWidth, maxWidth);
	        calcHeight = Math.min(calcHeight, maxHeight);

	        return new Rect(-tx / sx, -ty / sy, calcWidth / sx, calcHeight / sy);
	    },

	    transformToFitContent: function(opt) {
	        opt || (opt = {});

	        var contentBBox, contentLocalOrigin;
	        if ('contentArea' in opt) {
	            var contentArea = opt.contentArea;
	            contentBBox = this.localToPaperRect(contentArea);
	            contentLocalOrigin = new Point(contentArea);
	        } else {
	            contentBBox = this.getContentBBox(opt);
	            contentLocalOrigin = this.paperToLocalPoint(contentBBox);
	        }

	        if (!contentBBox.width || !contentBBox.height) { return; }

	        defaults(opt, {
	            padding: 0,
	            preserveAspectRatio: true,
	            scaleGrid: null,
	            minScale: 0,
	            maxScale: Number.MAX_VALUE,
	            verticalAlign: 'top',
	            horizontalAlign: 'left',
	            //minScaleX
	            //minScaleY
	            //maxScaleX
	            //maxScaleY
	            //fittingBBox
	        });

	        var padding = normalizeSides(opt.padding);

	        var minScaleX = opt.minScaleX || opt.minScale;
	        var maxScaleX = opt.maxScaleX || opt.maxScale;
	        var minScaleY = opt.minScaleY || opt.minScale;
	        var maxScaleY = opt.maxScaleY || opt.maxScale;

	        var fittingBBox;
	        if (opt.fittingBBox) {
	            fittingBBox = opt.fittingBBox;
	        } else {
	            var currentTranslate = this.translate();
	            var computedSize = this.getComputedSize();
	            fittingBBox = {
	                x: currentTranslate.tx,
	                y: currentTranslate.ty,
	                width: computedSize.width,
	                height: computedSize.height
	            };
	        }

	        fittingBBox = new Rect(fittingBBox).moveAndExpand({
	            x: padding.left,
	            y: padding.top,
	            width: -padding.left - padding.right,
	            height: -padding.top - padding.bottom
	        });

	        var currentScale = this.scale();

	        var newSx = fittingBBox.width / contentBBox.width * currentScale.sx;
	        var newSy = fittingBBox.height / contentBBox.height * currentScale.sy;

	        if (opt.preserveAspectRatio) {
	            newSx = newSy = Math.min(newSx, newSy);
	        }

	        // snap scale to a grid
	        if (opt.scaleGrid) {

	            var gridSize = opt.scaleGrid;

	            newSx = gridSize * Math.floor(newSx / gridSize);
	            newSy = gridSize * Math.floor(newSy / gridSize);
	        }

	        // scale min/max boundaries
	        newSx = Math.min(maxScaleX, Math.max(minScaleX, newSx));
	        newSy = Math.min(maxScaleY, Math.max(minScaleY, newSy));

	        var scaleDiff = {
	            x: newSx / currentScale.sx,
	            y: newSy / currentScale.sy
	        };

	        var origin = this.options.origin;
	        var newOx = fittingBBox.x - contentLocalOrigin.x * newSx - origin.x;
	        var newOy = fittingBBox.y - contentLocalOrigin.y * newSy - origin.y;

	        switch (opt.verticalAlign) {
	            case 'middle':
	                newOy = newOy + (fittingBBox.height - contentBBox.height * scaleDiff.y) / 2;
	                break;
	            case 'bottom':
	                newOy = newOy + (fittingBBox.height - contentBBox.height * scaleDiff.y);
	                break;
	            case 'top':
	            default:
	                break;
	        }

	        switch (opt.horizontalAlign) {
	            case 'middle':
	                newOx = newOx + (fittingBBox.width - contentBBox.width * scaleDiff.x) / 2;
	                break;
	            case 'right':
	                newOx = newOx + (fittingBBox.width - contentBBox.width * scaleDiff.x);
	                break;
	            case 'left':
	            default:
	                break;
	        }

	        this.scale(newSx, newSy);
	        this.translate(newOx, newOy);
	    },

	    scaleContentToFit: function(opt) {
	        this.transformToFitContent(opt);
	    },

	    // Return the dimensions of the content area in local units (without transformations).
	    getContentArea: function(opt) {

	        if (opt && opt.useModelGeometry) {
	            return this.model.getBBox() || new Rect();
	        }

	        return V(this.cells).getBBox();
	    },

	    // Return the dimensions of the content bbox in the paper units (as it appears on screen).
	    getContentBBox: function(opt) {

	        return this.localToPaperRect(this.getContentArea(opt));
	    },

	    // Returns a geometry rectangle representing the entire
	    // paper area (coordinates from the left paper border to the right one
	    // and the top border to the bottom one).
	    getArea: function() {

	        return this.paperToLocalRect(this.getComputedSize());
	    },

	    getRestrictedArea: function() {
	        var args = [], len = arguments.length;
	        while ( len-- ) args[ len ] = arguments[ len ];


	        var ref = this.options;
	        var restrictTranslate = ref.restrictTranslate;

	        var restrictedArea;
	        if (isFunction(restrictTranslate)) {
	            // A method returning a bounding box
	            restrictedArea = restrictTranslate.apply(this, args);
	        } else if (restrictTranslate === true) {
	            // The paper area
	            restrictedArea = this.getArea();
	        } else if (!restrictTranslate) {
	            // falsy value
	            restrictedArea = null;
	        } else {
	            // any other value
	            restrictedArea = new Rect(restrictTranslate);
	        }

	        return restrictedArea;
	    },

	    createViewForModel: function(cell) {

	        var ref = this;
	        var options = ref.options;
	        // A class taken from the paper options.
	        var optionalViewClass;

	        // A default basic class (either dia.ElementView or dia.LinkView)
	        var defaultViewClass;

	        // A special class defined for this model in the corresponding namespace.
	        // e.g. joint.shapes.basic.Rect searches for joint.shapes.basic.RectView
	        var namespace = options.cellViewNamespace;
	        var type = cell.get('type') + 'View';
	        var namespaceViewClass = getByPath(namespace, type, '.');

	        if (cell.isLink()) {
	            optionalViewClass = options.linkView;
	            defaultViewClass = LinkView;
	        } else {
	            optionalViewClass = options.elementView;
	            defaultViewClass = ElementView;
	        }

	        // a) the paper options view is a class (deprecated)
	        //  1. search the namespace for a view
	        //  2. if no view was found, use view from the paper options
	        // b) the paper options view is a function
	        //  1. call the function from the paper options
	        //  2. if no view was return, search the namespace for a view
	        //  3. if no view was found, use the default
	        var ViewClass = (optionalViewClass.prototype instanceof Backbone.View)
	            ? namespaceViewClass || optionalViewClass
	            : optionalViewClass.call(this, cell) || namespaceViewClass || defaultViewClass;

	        return new ViewClass({
	            model: cell,
	            interactive: options.interactive,
	            labelsLayer: options.labelsLayer === true ? LayersNames.LABELS : options.labelsLayer
	        });
	    },

	    removeView: function(cell) {

	        var id = cell.id;
	        var ref = this;
	        var _views = ref._views;
	        var _updates = ref._updates;
	        var view = _views[id];
	        if (view) {
	            var cid = view.cid;
	            var mounted = _updates.mounted;
	            var unmounted = _updates.unmounted;
	            view.remove();
	            delete _views[id];
	            delete mounted[cid];
	            delete unmounted[cid];
	        }
	        return view;
	    },

	    renderView: function(cell, opt) {

	        var id = cell.id;
	        var views = this._views;
	        var view, flag;
	        var create = true;
	        if (id in views) {
	            view = views[id];
	            if (view.model === cell) {
	                flag = view.FLAG_INSERT;
	                create = false;
	            } else {
	                // The view for this `id` already exist.
	                // The cell is a new instance of the model with identical id
	                // We simply remove the existing view and create a new one
	                this.removeView(cell);
	            }
	        }
	        if (create) {
	            view = views[id] = this.createViewForModel(cell);
	            view.paper = this;
	            flag = this.registerUnmountedView(view) | this.FLAG_INIT | view.getFlag(result(view, 'initFlag'));
	        }
	        this.requestViewUpdate(view, flag, view.UPDATE_PRIORITY, opt);
	        return view;
	    },

	    onImageDragStart: function() {
	        // This is the only way to prevent image dragging in Firefox that works.
	        // Setting -moz-user-select: none, draggable="false" attribute or user-drag: none didn't help.

	        return false;
	    },

	    resetViews: function(cells, opt) {
	        opt || (opt = {});
	        cells || (cells = []);
	        this._resetUpdates();
	        // clearing views removes any event listeners
	        this.removeViews();
	        // Allows to unfreeze normally while in the idle state using autoFreeze option
	        var key = this.options.autoFreeze ? null : 'reset';
	        this.freeze({ key: key });
	        for (var i = 0, n = cells.length; i < n; i++) {
	            this.renderView(cells[i], opt);
	        }
	        this.unfreeze({ key: key });
	        this.sortViews();
	    },

	    removeViews: function() {

	        invoke(this._views, 'remove');

	        this._views = {};
	    },

	    sortViews: function() {

	        if (!this.isExactSorting()) {
	            // noop
	            return;
	        }
	        if (this.isFrozen()) {
	            // sort views once unfrozen
	            this._updates.sort = true;
	            return;
	        }
	        this.sortViewsExact();
	    },

	    sortViewsExact: function() {

	        // Run insertion sort algorithm in order to efficiently sort DOM elements according to their
	        // associated model `z` attribute.

	        var $cells = $(this.cells).children('[model-id]');
	        var cells = this.model.get('cells');

	        sortElements($cells, function(a, b) {
	            var cellA = cells.get(a.getAttribute('model-id'));
	            var cellB = cells.get(b.getAttribute('model-id'));
	            var zA = cellA.attributes.z || 0;
	            var zB = cellB.attributes.z || 0;
	            return (zA === zB) ? 0 : (zA < zB) ? -1 : 1;
	        });
	    },

	    insertView: function(view, isInitialInsert) {
	        var layerView = this.getLayerView(LayersNames.CELLS);
	        var el = view.el;
	        var model = view.model;
	        switch (this.options.sorting) {
	            case sortingTypes.APPROX:
	                layerView.insertSortedNode(el, model.get('z'));
	                break;
	            case sortingTypes.EXACT:
	            default:
	                layerView.insertNode(el);
	                break;
	        }
	        view.onMount(isInitialInsert);
	    },

	    detachView: function detachView(view) {
	        view.unmount();
	        view.onDetach();
	    },

	    scale: function(sx, sy, ox, oy) {

	        // getter
	        if (sx === undefined) {
	            return V.matrixToScale(this.matrix());
	        }

	        // setter
	        if (sy === undefined) {
	            sy = sx;
	        }
	        if (ox === undefined) {
	            ox = 0;
	            oy = 0;
	        }

	        var translate = this.translate();

	        if (ox || oy || translate.tx || translate.ty) {
	            var newTx = translate.tx - ox * (sx - 1);
	            var newTy = translate.ty - oy * (sy - 1);
	            this.translate(newTx, newTy);
	        }

	        sx = Math.max(sx || 0, this.MIN_SCALE);
	        sy = Math.max(sy || 0, this.MIN_SCALE);

	        var ctm = this.matrix();
	        ctm.a = sx;
	        ctm.d = sy;

	        this.matrix(ctm);

	        this.trigger('scale', sx, sy, ox, oy);

	        return this;
	    },

	    // Experimental - do not use in production.
	    rotate: function(angle, cx, cy) {

	        // getter
	        if (angle === undefined) {
	            return V.matrixToRotate(this.matrix());
	        }

	        // setter

	        // If the origin is not set explicitely, rotate around the center. Note that
	        // we must use the plain bounding box (`this.el.getBBox()` instead of the one that gives us
	        // the real bounding box (`bbox()`) including transformations).
	        if (cx === undefined) {
	            var bbox = this.cells.getBBox();
	            cx = bbox.width / 2;
	            cy = bbox.height / 2;
	        }

	        var ctm = this.matrix().translate(cx, cy).rotate(angle).translate(-cx, -cy);
	        this.matrix(ctm);

	        return this;
	    },

	    translate: function(tx, ty) {

	        // getter
	        if (tx === undefined) {
	            return V.matrixToTranslate(this.matrix());
	        }

	        var ref = this;
	        var options = ref.options;
	        var origin = options.origin;
	        var drawGrid = options.drawGrid;

	        // setter
	        tx || (tx = 0);
	        ty || (ty = 0);

	        var ctm = this.matrix();
	        if (ctm.e === tx && ctm.f === ty) { return this; }
	        ctm.e = tx;
	        ctm.f = ty;

	        this.matrix(ctm);

	        var ref$1 = this.translate();
	        var ox = ref$1.tx;
	        var oy = ref$1.ty;
	        origin.x = ox;
	        origin.y = oy;

	        this.trigger('translate', ox, oy);

	        if (drawGrid) {
	            this.drawGrid();
	        }

	        return this;
	    },

	    // Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also
	    // be a selector or a jQuery object.
	    findView: function($el) {

	        var el = isString($el)
	            ? this.cells.querySelector($el)
	            : $el instanceof $ ? $el[0] : $el;

	        var id = this.findAttribute('model-id', el);
	        if (id) { return this._views[id]; }

	        return undefined;
	    },

	    // Find a view for a model `cell`. `cell` can also be a string or number representing a model `id`.
	    findViewByModel: function(cell) {

	        var id = (isString(cell) || isNumber(cell)) ? cell : (cell && cell.id);

	        return this._views[id];
	    },

	    // Find all views at given point
	    findViewsFromPoint: function(p) {

	        p = new Point(p);

	        var views = this.model.getElements().map(this.findViewByModel, this);

	        return views.filter(function(view) {
	            return view && view.vel.getBBox({ target: this.cells }).containsPoint(p);
	        }, this);
	    },

	    // Find all views in given area
	    findViewsInArea: function(rect, opt) {

	        opt = defaults(opt || {}, { strict: false });
	        rect = new Rect(rect);

	        var views = this.model.getElements().map(this.findViewByModel, this);
	        var method = opt.strict ? 'containsRect' : 'intersect';

	        return views.filter(function(view) {
	            return view && rect[method](view.vel.getBBox({ target: this.cells }));
	        }, this);
	    },

	    removeTools: function() {
	        this.dispatchToolsEvent('remove');
	        return this;
	    },

	    hideTools: function() {
	        this.dispatchToolsEvent('hide');
	        return this;
	    },

	    showTools: function() {
	        this.dispatchToolsEvent('show');
	        return this;
	    },

	    dispatchToolsEvent: function(event) {
	        var ref;

	        var args = [], len = arguments.length - 1;
	        while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
	        if (typeof event !== 'string') { return; }
	        (ref = this).trigger.apply(ref, [ 'tools:event', event ].concat( args ));
	    },


	    getModelById: function(id) {

	        return this.model.getCell(id);
	    },

	    snapToGrid: function(x, y) {

	        // Convert global coordinates to the local ones of the `viewport`. Otherwise,
	        // improper transformation would be applied when the viewport gets transformed (scaled/rotated).
	        return this.clientToLocalPoint(x, y).snapToGrid(this.options.gridSize);
	    },

	    localToPaperPoint: function(x, y) {
	        // allow `x` to be a point and `y` undefined
	        var localPoint = new Point(x, y);
	        var paperPoint = V.transformPoint(localPoint, this.matrix());
	        return paperPoint;
	    },

	    localToPaperRect: function(x, y, width, height) {
	        // allow `x` to be a rectangle and rest arguments undefined
	        var localRect = new Rect(x, y, width, height);
	        var paperRect = V.transformRect(localRect, this.matrix());
	        return paperRect;
	    },

	    paperToLocalPoint: function(x, y) {
	        // allow `x` to be a point and `y` undefined
	        var paperPoint = new Point(x, y);
	        var localPoint = V.transformPoint(paperPoint, this.matrix().inverse());
	        return localPoint;
	    },

	    paperToLocalRect: function(x, y, width, height) {
	        // allow `x` to be a rectangle and rest arguments undefined
	        var paperRect = new Rect(x, y, width, height);
	        var localRect = V.transformRect(paperRect, this.matrix().inverse());
	        return localRect;
	    },

	    localToClientPoint: function(x, y) {
	        // allow `x` to be a point and `y` undefined
	        var localPoint = new Point(x, y);
	        var clientPoint = V.transformPoint(localPoint, this.clientMatrix());
	        return clientPoint;
	    },

	    localToClientRect: function(x, y, width, height) {
	        // allow `x` to be a point and `y` undefined
	        var localRect = new Rect(x, y, width, height);
	        var clientRect = V.transformRect(localRect, this.clientMatrix());
	        return clientRect;
	    },

	    // Transform client coordinates to the paper local coordinates.
	    // Useful when you have a mouse event object and you'd like to get coordinates
	    // inside the paper that correspond to `evt.clientX` and `evt.clientY` point.
	    // Example: var localPoint = paper.clientToLocalPoint({ x: evt.clientX, y: evt.clientY });
	    clientToLocalPoint: function(x, y) {
	        // allow `x` to be a point and `y` undefined
	        var clientPoint = new Point(x, y);
	        var localPoint = V.transformPoint(clientPoint, this.clientMatrix().inverse());
	        return localPoint;
	    },

	    clientToLocalRect: function(x, y, width, height) {
	        // allow `x` to be a point and `y` undefined
	        var clientRect = new Rect(x, y, width, height);
	        var localRect = V.transformRect(clientRect, this.clientMatrix().inverse());
	        return localRect;
	    },

	    localToPagePoint: function(x, y) {

	        return this.localToPaperPoint(x, y).offset(this.pageOffset());
	    },

	    localToPageRect: function(x, y, width, height) {

	        return this.localToPaperRect(x, y, width, height).offset(this.pageOffset());
	    },

	    pageToLocalPoint: function(x, y) {

	        var pagePoint = new Point(x, y);
	        var paperPoint = pagePoint.difference(this.pageOffset());
	        return this.paperToLocalPoint(paperPoint);
	    },

	    pageToLocalRect: function(x, y, width, height) {

	        var pageOffset = this.pageOffset();
	        var paperRect = new Rect(x, y, width, height);
	        paperRect.x -= pageOffset.x;
	        paperRect.y -= pageOffset.y;
	        return this.paperToLocalRect(paperRect);
	    },

	    clientOffset: function() {

	        var clientRect = this.svg.getBoundingClientRect();
	        return new Point(clientRect.left, clientRect.top);
	    },

	    pageOffset: function() {

	        return this.clientOffset().offset(window.scrollX, window.scrollY);
	    },

	    linkAllowed: function(linkView) {

	        if (!(linkView instanceof LinkView)) {
	            throw new Error('Must provide a linkView.');
	        }

	        var link = linkView.model;
	        var paperOptions = this.options;
	        var graph = this.model;
	        var ns = graph.constructor.validations;

	        if (!paperOptions.multiLinks) {
	            if (!ns.multiLinks.call(this, graph, link)) { return false; }
	        }

	        if (!paperOptions.linkPinning) {
	            // Link pinning is not allowed and the link is not connected to the target.
	            if (!ns.linkPinning.call(this, graph, link)) { return false; }
	        }

	        if (typeof paperOptions.allowLink === 'function') {
	            if (!paperOptions.allowLink.call(this, linkView, this)) { return false; }
	        }

	        return true;
	    },

	    getDefaultLink: function(cellView, magnet) {

	        return isFunction(this.options.defaultLink)
	        // default link is a function producing link model
	            ? this.options.defaultLink.call(this, cellView, magnet)
	        // default link is the Backbone model
	            : this.options.defaultLink.clone();
	    },

	    // Cell highlighting.
	    // ------------------

	    resolveHighlighter: function(opt) {
	        if ( opt === void 0 ) opt = {};


	        var highlighterDef = opt.highlighter;
	        var type = opt.type;
	        var ref = this.options;
	        var highlighting = ref.highlighting;
	        var highlighterNamespace = ref.highlighterNamespace;

	        /*
	            Expecting opt.highlighter to have the following structure:
	            {
	                name: 'highlighter-name',
	                options: {
	                    some: 'value'
	                }
	            }
	        */
	        if (highlighterDef === undefined) {

	            // Is highlighting disabled?
	            if (!highlighting) { return false; }
	            // check for built-in types
	            if (type) {
	                highlighterDef = highlighting[type];
	                // Is a specific type highlight disabled?
	                if (highlighterDef === false) { return false; }
	            }
	            if (!highlighterDef) {
	                // Type not defined use default highlight
	                highlighterDef = highlighting['default'];
	            }
	        }

	        // Do nothing if opt.highlighter is falsy.
	        // This allows the case to not highlight cell(s) in certain cases.
	        // For example, if you want to NOT highlight when embedding elements
	        // or use a custom highlighter.
	        if (!highlighterDef) { return false; }

	        // Allow specifying a highlighter by name.
	        if (isString(highlighterDef)) {
	            highlighterDef = {
	                name: highlighterDef
	            };
	        }

	        var name = highlighterDef.name;
	        var highlighter = highlighterNamespace[name];

	        // Highlighter validation
	        if (!highlighter) {
	            throw new Error('Unknown highlighter ("' + name + '")');
	        }
	        if (typeof highlighter.highlight !== 'function') {
	            throw new Error('Highlighter ("' + name + '") is missing required highlight() method');
	        }
	        if (typeof highlighter.unhighlight !== 'function') {
	            throw new Error('Highlighter ("' + name + '") is missing required unhighlight() method');
	        }

	        return {
	            highlighter: highlighter,
	            options: highlighterDef.options || {},
	            name: name
	        };
	    },

	    onCellHighlight: function(cellView, magnetEl, opt) {
	        var highlighterDescriptor = this.resolveHighlighter(opt);
	        if (!highlighterDescriptor) { return; }
	        var highlighter = highlighterDescriptor.highlighter;
	        var options = highlighterDescriptor.options;
	        highlighter.highlight(cellView, magnetEl, options);
	    },

	    onCellUnhighlight: function(cellView, magnetEl, opt) {
	        var highlighterDescriptor = this.resolveHighlighter(opt);
	        if (!highlighterDescriptor) { return; }
	        var highlighter = highlighterDescriptor.highlighter;
	        var options = highlighterDescriptor.options;
	        highlighter.unhighlight(cellView, magnetEl, options);
	    },

	    // Interaction.
	    // ------------

	    pointerdblclick: function(evt) {

	        evt.preventDefault();

	        // magnetpointerdblclick can stop propagation

	        evt = normalizeEvent(evt);

	        var view = this.findView(evt.target);
	        if (this.guard(evt, view)) { return; }

	        var localPoint = this.snapToGrid(evt.clientX, evt.clientY);

	        if (view) {
	            view.pointerdblclick(evt, localPoint.x, localPoint.y);

	        } else {
	            this.trigger('blank:pointerdblclick', evt, localPoint.x, localPoint.y);
	        }
	    },

	    pointerclick: function(evt) {

	        // magnetpointerclick can stop propagation

	        var data = this.eventData(evt);
	        // Trigger event only if mouse has not moved.
	        if (data.mousemoved <= this.options.clickThreshold) {

	            evt = normalizeEvent(evt);

	            var view = this.findView(evt.target);
	            if (this.guard(evt, view)) { return; }

	            var localPoint = this.snapToGrid(evt.clientX, evt.clientY);

	            if (view) {
	                view.pointerclick(evt, localPoint.x, localPoint.y);

	            } else {
	                this.trigger('blank:pointerclick', evt, localPoint.x, localPoint.y);
	            }
	        }
	    },

	    contextmenu: function(evt) {

	        if (this.options.preventContextMenu) { evt.preventDefault(); }

	        if (this.contextMenuFired) {
	            this.contextMenuFired = false;
	            return;
	        }

	        evt = normalizeEvent(evt);

	        this.contextMenuTrigger(evt);
	    },

	    contextMenuTrigger: function(evt) {
	        var view = this.findView(evt.target);
	        if (this.guard(evt, view)) { return; }

	        var localPoint = this.snapToGrid(evt.clientX, evt.clientY);

	        if (view) {
	            view.contextmenu(evt, localPoint.x, localPoint.y);

	        } else {
	            this.trigger('blank:contextmenu', evt, localPoint.x, localPoint.y);
	        }
	    },

	    pointerdown: function(evt) {

	        evt = normalizeEvent(evt);

	        var target = evt.target;
	        var button = evt.button;
	        var view = this.findView(target);
	        var isContextMenu = (button === 2);

	        if (view) {

	            if (!isContextMenu && this.guard(evt, view)) { return; }

	            var isTargetFormNode = this.FORM_CONTROL_TAG_NAMES.includes(target.tagName);

	            if (this.options.preventDefaultViewAction && !isTargetFormNode) {
	                // If the target is a form element, we do not want to prevent the default action.
	                // For example, we want to be able to select text in a text input or
	                // to be able to click on a checkbox.
	                evt.preventDefault();
	            }

	            if (isTargetFormNode) {
	                // If the target is a form element, we do not want to start dragging the element.
	                // For example, we want to be able to select text by dragging the mouse.
	                view.preventDefaultInteraction(evt);
	            }

	            var rootViewEl = view.el;

	            // Custom event
	            var eventNode = target.closest('[event]');
	            if (eventNode && rootViewEl !== eventNode && view.el.contains(eventNode)) {
	                var eventEvt = normalizeEvent($.Event(evt.originalEvent, {
	                    data: evt.data,
	                    // Originally the event listener was attached to the event element.
	                    currentTarget: eventNode
	                }));
	                this.onevent(eventEvt);
	                if (eventEvt.isDefaultPrevented()) {
	                    evt.preventDefault();
	                }
	                // `onevent` can stop propagation
	                if (eventEvt.isPropagationStopped()) { return; }
	                evt.data = eventEvt.data;
	            }

	            // Element magnet
	            var magnetNode = target.closest('[magnet]');
	            if (magnetNode && view.el !== magnetNode && view.el.contains(magnetNode)) {
	                var magnetEvt = normalizeEvent($.Event(evt.originalEvent, {
	                    data: evt.data,
	                    // Originally the event listener was attached to the magnet element.
	                    currentTarget: magnetNode
	                }));
	                this.onmagnet(magnetEvt);
	                if (magnetEvt.isDefaultPrevented()) {
	                    evt.preventDefault();
	                }
	                // `onmagnet` stops propagation when `addLinkFromMagnet` is allowed
	                if (magnetEvt.isPropagationStopped()) {
	                    // `magnet:pointermove` and `magnet:pointerup` events must be fired
	                    if (isContextMenu) { return; }
	                    this.delegateDragEvents(view, magnetEvt.data);
	                    return;
	                }
	                evt.data = magnetEvt.data;
	            }
	        }

	        if (isContextMenu) {
	            this.contextMenuFired = true;
	            var contextmenuEvt = $.Event(evt.originalEvent, { type: 'contextmenu', data: evt.data });
	            this.contextMenuTrigger(contextmenuEvt);
	        } else {
	            var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
	            if (view) {
	                view.pointerdown(evt, localPoint.x, localPoint.y);
	            } else {
	                if (this.options.preventDefaultBlankAction) {
	                    evt.preventDefault();
	                }
	                this.trigger('blank:pointerdown', evt, localPoint.x, localPoint.y);
	            }

	            this.delegateDragEvents(view, evt.data);
	        }

	    },

	    pointermove: function(evt) {

	        // mouse moved counter
	        var data = this.eventData(evt);
	        if (!data.mousemoved) {
	            data.mousemoved = 0;
	            // Make sure that events like `mouseenter` and `mouseleave` are
	            // not triggered while the user is dragging a cellView.
	            this.undelegateEvents();
	            // Note: the events are undelegated after the first `pointermove` event.
	            // Not on `pointerdown` to make sure that `dbltap` is recognized.
	        }

	        var mousemoved = ++data.mousemoved;

	        if (mousemoved <= this.options.moveThreshold) { return; }

	        evt = normalizeEvent(evt);

	        var localPoint = this.snapToGrid(evt.clientX, evt.clientY);

	        var view = data.sourceView;
	        if (view) {
	            view.pointermove(evt, localPoint.x, localPoint.y);
	        } else {
	            this.trigger('blank:pointermove', evt, localPoint.x, localPoint.y);
	        }

	        this.eventData(evt, data);
	    },

	    pointerup: function(evt) {

	        this.undelegateDocumentEvents();

	        var normalizedEvt = normalizeEvent(evt);

	        var localPoint = this.snapToGrid(normalizedEvt.clientX, normalizedEvt.clientY);

	        var view = this.eventData(evt).sourceView;
	        if (view) {
	            view.pointerup(normalizedEvt, localPoint.x, localPoint.y);
	        } else {
	            this.trigger('blank:pointerup', normalizedEvt, localPoint.x, localPoint.y);
	        }

	        if (!normalizedEvt.isPropagationStopped()) {
	            this.pointerclick($.Event(evt.originalEvent, { type: 'click', data: evt.data }));
	        }

	        evt.stopImmediatePropagation();
	        this.delegateEvents();
	    },

	    mouseover: function(evt) {

	        evt = normalizeEvent(evt);

	        var view = this.findView(evt.target);
	        if (this.guard(evt, view)) { return; }

	        if (view) {
	            view.mouseover(evt);

	        } else {
	            if (this.el === evt.target) { return; } // prevent border of paper from triggering this
	            this.trigger('blank:mouseover', evt);
	        }
	    },

	    mouseout: function(evt) {

	        evt = normalizeEvent(evt);

	        var view = this.findView(evt.target);
	        if (this.guard(evt, view)) { return; }

	        if (view) {
	            view.mouseout(evt);

	        } else {
	            if (this.el === evt.target) { return; } // prevent border of paper from triggering this
	            this.trigger('blank:mouseout', evt);
	        }
	    },

	    mouseenter: function(evt) {

	        evt = normalizeEvent(evt);

	        var target = evt.target;
	        var relatedTarget = evt.relatedTarget;
	        var currentTarget = evt.currentTarget;
	        var view = this.findView(target);
	        if (this.guard(evt, view)) { return; }
	        var relatedView = this.findView(relatedTarget);
	        if (view) {
	            if (relatedView === view) {
	                // Mouse left a cell tool
	                return;
	            }
	            view.mouseenter(evt);
	            if (this.el.contains(relatedTarget)) {
	                // The pointer remains inside the paper.
	                return;
	            }
	        }
	        if (relatedView) {
	            return;
	        }
	        // prevent double `mouseenter` event if the `relatedTarget` is outside the paper
	        // (mouseenter method would be fired twice)
	        if (currentTarget === this.el) {
	            // `paper` (more descriptive), not `blank`
	            this.trigger('paper:mouseenter', evt);
	        }
	    },

	    mouseleave: function(evt) {

	        evt = normalizeEvent(evt);

	        var target = evt.target;
	        var relatedTarget = evt.relatedTarget;
	        var currentTarget = evt.currentTarget;
	        var view = this.findView(target);
	        if (this.guard(evt, view)) { return; }
	        var relatedView = this.findView(relatedTarget);
	        if (view) {
	            if (relatedView === view) {
	                // Mouse entered a cell tool
	                return;
	            }
	            view.mouseleave(evt);
	            if (this.el.contains(relatedTarget)) {
	                // The pointer has exited a cellView. The pointer is still inside of the paper.
	                return;
	            }
	        }
	        if (relatedView) {
	            // The pointer has entered a new cellView
	            return;
	        }
	        // prevent double `mouseleave` event if the `relatedTarget` is outside the paper
	        // (mouseleave method would be fired twice)
	        if (currentTarget === this.el) {
	            // There is no cellView under the pointer, nor the blank area of the paper
	            this.trigger('paper:mouseleave', evt);
	        }
	    },

	    _processMouseWheelEvtBuf: debounce(function() {
	        var ref = this._mw_evt_buffer;
	        var event = ref.event;
	        var deltas = ref.deltas;
	        var deltaY = deltas.reduce(function (acc, deltaY) { return acc + cap(deltaY, WHEEL_CAP); }, 0);

	        var scale = Math.pow(0.995, deltaY); // 1.005 for inverted pinch/zoom
	        var ref$1 = this.clientToLocalPoint(event.clientX, event.clientY);
	        var x = ref$1.x;
	        var y = ref$1.y;
	        this.trigger('paper:pinch', event, x, y, scale);

	        this._mw_evt_buffer = {
	            event: null,
	            deltas: [],
	        };
	    }, WHEEL_WAIT_MS, { maxWait: WHEEL_WAIT_MS }),

	    mousewheel: function(evt) {

	        evt = normalizeEvent(evt);

	        var view = this.findView(evt.target);
	        if (this.guard(evt, view)) { return; }

	        var originalEvent = evt.originalEvent;
	        var localPoint = this.snapToGrid(originalEvent.clientX, originalEvent.clientY);
	        var ref = normalizeWheel(originalEvent);
	        var deltaX = ref.deltaX;
	        var deltaY = ref.deltaY;

	        var pinchHandlers = this._events['paper:pinch'];

	        // Touchpad devices will send a fake CTRL press when a pinch is performed
	        //
	        // We also check if there are any subscribers to paper:pinch event. If there are none,
	        // just skip the entire block of code (we don't want to blindly call
	        // .preventDefault() if we really don't have to).
	        if (evt.ctrlKey && pinchHandlers && pinchHandlers.length > 0) {
	            // This is a pinch gesture, it's safe to assume that we must call .preventDefault()
	            originalEvent.preventDefault();
	            this._mw_evt_buffer.event = originalEvent;
	            this._mw_evt_buffer.deltas.push(deltaY);
	            this._processMouseWheelEvtBuf();
	        } else {
	            var delta = Math.max(-1, Math.min(1, originalEvent.wheelDelta));
	            if (view) {
	                view.mousewheel(evt, localPoint.x, localPoint.y, delta);

	            } else {
	                this.trigger('blank:mousewheel', evt, localPoint.x, localPoint.y, delta);
	            }

	            this.trigger('paper:pan', evt, deltaX, deltaY);
	        }
	    },

	    onevent: function(evt) {

	        var eventNode = evt.currentTarget;
	        var eventName = eventNode.getAttribute('event');
	        if (eventName) {
	            var view = this.findView(eventNode);
	            if (view) {

	                evt = normalizeEvent(evt);
	                if (this.guard(evt, view)) { return; }

	                var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
	                view.onevent(evt, eventName, localPoint.x, localPoint.y);
	            }
	        }
	    },

	    magnetEvent: function(evt, handler) {

	        var magnetNode = evt.currentTarget;
	        var magnetValue = magnetNode.getAttribute('magnet');
	        if (magnetValue) {
	            var view = this.findView(magnetNode);
	            if (view) {
	                evt = normalizeEvent(evt);
	                if (this.guard(evt, view)) { return; }
	                var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
	                handler.call(this, view, evt, magnetNode, localPoint.x, localPoint.y);
	            }
	        }
	    },

	    onmagnet: function(evt) {

	        if (evt.button === 2) {
	            this.contextMenuFired = true;
	            this.magnetContextMenuFired = true;
	            var contextmenuEvt = $.Event(evt.originalEvent, {
	                type: 'contextmenu',
	                data: evt.data,
	                currentTarget: evt.currentTarget,
	            });
	            this.magnetContextMenuTrigger(contextmenuEvt);
	            if (contextmenuEvt.isPropagationStopped()) {
	                evt.stopPropagation();
	            }
	        } else {
	            this.magnetEvent(evt, function(view, evt, _, x, y) {
	                view.onmagnet(evt, x, y);
	            });
	        }
	    },

	    magnetpointerdblclick: function(evt) {

	        this.magnetEvent(evt, function(view, evt, magnet, x, y) {
	            view.magnetpointerdblclick(evt, magnet, x, y);
	        });
	    },

	    magnetcontextmenu: function(evt) {
	        if (this.options.preventContextMenu) { evt.preventDefault(); }

	        if (this.magnetContextMenuFired) {
	            this.magnetContextMenuFired = false;
	            return;
	        }

	        this.magnetContextMenuTrigger(evt);
	    },

	    magnetContextMenuTrigger: function(evt) {
	        this.magnetEvent(evt, function(view, evt, magnet, x, y) {
	            view.magnetcontextmenu(evt, magnet, x, y);
	        });
	    },

	    onlabel: function(evt) {

	        var labelNode = evt.currentTarget;
	        var view = this.findView(labelNode);
	        if (view) {

	            evt = normalizeEvent(evt);
	            if (this.guard(evt, view)) { return; }

	            var localPoint = this.snapToGrid(evt.clientX, evt.clientY);
	            view.onlabel(evt, localPoint.x, localPoint.y);
	        }
	    },

	    getPointerArgs: function getPointerArgs(evt) {
	        var normalizedEvt = normalizeEvent(evt);
	        var ref = this.snapToGrid(normalizedEvt.clientX, normalizedEvt.clientY);
	        var x = ref.x;
	        var y = ref.y;
	        return [normalizedEvt, x, y];
	    },

	    delegateDragEvents: function(view, data) {

	        data || (data = {});
	        this.eventData({ data: data }, { sourceView: view || null, mousemoved: 0 });
	        this.delegateDocumentEvents(null, data);
	    },

	    // Guard the specified event. If the event should be ignored, guard returns `true`.
	    // Otherwise, it returns `false`.
	    guard: function(evt, view) {

	        if (evt.type === 'mousedown' && evt.button === 2) {
	            // handled as `contextmenu` type
	            return true;
	        }

	        if (this.options.guard && this.options.guard(evt, view)) {
	            return true;
	        }

	        if (evt.data && evt.data.guarded !== undefined) {
	            return evt.data.guarded;
	        }

	        var target = evt.target;

	        if (this.GUARDED_TAG_NAMES.includes(target.tagName)) {
	            return true;
	        }

	        if (view && view.model && (view.model instanceof Cell)) {
	            return false;
	        }

	        if (this.svg === target || this.el === target || $.contains(this.svg, target)) {
	            return false;
	        }

	        return true;    // Event guarded. Paper should not react on it in any way.
	    },

	    setGridSize: function(gridSize) {

	        var ref = this;
	        var options = ref.options;
	        options.gridSize = gridSize;

	        if (options.drawGrid && !options.drawGridSize) {
	            // Do not redraw the grid if the `drawGridSize` is set.
	            this.drawGrid();
	        }

	        return this;
	    },

	    clearGrid: function() {

	        if (this.$grid) {
	            this.$grid.css('backgroundImage', 'none');
	        }
	        return this;
	    },

	    _getGridRefs: function() {

	        if (!this._gridCache) {

	            this._gridCache = {
	                root: V('svg', { width: '100%', height: '100%' }, V('defs')),
	                patterns: {},
	                add: function(id, vel) {
	                    V(this.root.node.childNodes[0]).append(vel);
	                    this.patterns[id] = vel;
	                    this.root.append(V('rect', { width: '100%', height: '100%', fill: 'url(#' + id + ')' }));
	                },
	                get: function(id) {
	                    return this.patterns[id];
	                },
	                exist: function(id) {
	                    return this.patterns[id] !== undefined;
	                }
	            };
	        }

	        return this._gridCache;
	    },

	    setGrid: function(drawGrid) {

	        this.clearGrid();

	        this._gridCache = null;
	        this._gridSettings = [];

	        var optionsList = Array.isArray(drawGrid) ? drawGrid : [drawGrid || {}];
	        optionsList.forEach(function(item) {
	            this._gridSettings.push.apply(this._gridSettings, this._resolveDrawGridOption(item));
	        }, this);
	        return this;
	    },

	    _resolveDrawGridOption: function(opt) {

	        var namespace = this.constructor.gridPatterns;
	        if (isString(opt) && Array.isArray(namespace[opt])) {
	            return namespace[opt].map(function(item) {
	                return assign({}, item);
	            });
	        }

	        var options = opt || { args: [{}] };
	        var isArray = Array.isArray(options);
	        var name = options.name;

	        if (!isArray && !name && !options.markup) {
	            name = 'dot';
	        }

	        if (name && Array.isArray(namespace[name])) {
	            var pattern = namespace[name].map(function(item) {
	                return assign({}, item);
	            });

	            var args = Array.isArray(options.args) ? options.args : [options.args || {}];

	            defaults(args[0], omit(opt, 'args'));
	            for (var i = 0; i < args.length; i++) {
	                if (pattern[i]) {
	                    assign(pattern[i], args[i]);
	                }
	            }
	            return pattern;
	        }

	        return isArray ? options : [options];
	    },

	    drawGrid: function(opt) {

	        var gridSize = this.options.drawGridSize || this.options.gridSize;
	        if (gridSize <= 1) {
	            return this.clearGrid();
	        }

	        var localOptions = Array.isArray(opt) ? opt : [opt];

	        var ctm = this.matrix();
	        var refs = this._getGridRefs();

	        this._gridSettings.forEach(function(gridLayerSetting, index) {

	            var id = 'pattern_' + index;
	            var options = merge(gridLayerSetting, localOptions[index], {
	                sx: ctm.a || 1,
	                sy: ctm.d || 1,
	                ox: ctm.e || 0,
	                oy: ctm.f || 0
	            });

	            options.width = gridSize * (ctm.a || 1) * (options.scaleFactor || 1);
	            options.height = gridSize * (ctm.d || 1) * (options.scaleFactor || 1);

	            if (!refs.exist(id)) {
	                refs.add(id, V('pattern', { id: id, patternUnits: 'userSpaceOnUse' }, V(options.markup)));
	            }

	            var patternDefVel = refs.get(id);

	            if (isFunction(options.update)) {
	                options.update(patternDefVel.node.childNodes[0], options);
	            }

	            var x = options.ox % options.width;
	            if (x < 0) { x += options.width; }

	            var y = options.oy % options.height;
	            if (y < 0) { y += options.height; }

	            patternDefVel.attr({
	                x: x,
	                y: y,
	                width: options.width,
	                height: options.height
	            });
	        });

	        var patternUri = new XMLSerializer().serializeToString(refs.root.node);
	        patternUri = 'url(data:image/svg+xml;base64,' + btoa(patternUri) + ')';

	        this.$grid.css('backgroundImage', patternUri);

	        return this;
	    },

	    updateBackgroundImage: function(opt) {

	        opt = opt || {};

	        var backgroundPosition = opt.position || 'center';
	        var backgroundSize = opt.size || 'auto auto';

	        var currentScale = this.scale();
	        var currentTranslate = this.translate();

	        // backgroundPosition
	        if (isObject$1(backgroundPosition)) {
	            var x = currentTranslate.tx + (currentScale.sx * (backgroundPosition.x || 0));
	            var y = currentTranslate.ty + (currentScale.sy * (backgroundPosition.y || 0));
	            backgroundPosition = x + 'px ' + y + 'px';
	        }

	        // backgroundSize
	        if (isObject$1(backgroundSize)) {
	            backgroundSize = new Rect(backgroundSize).scale(currentScale.sx, currentScale.sy);
	            backgroundSize = backgroundSize.width + 'px ' + backgroundSize.height + 'px';
	        }

	        this.$background.css({
	            backgroundSize: backgroundSize,
	            backgroundPosition: backgroundPosition
	        });
	    },

	    drawBackgroundImage: function(img, opt) {

	        // Clear the background image if no image provided
	        if (!(img instanceof HTMLImageElement)) {
	            this.$background.css('backgroundImage', '');
	            return;
	        }

	        if (!this._background || this._background.id !== opt.id) {
	            // Draw only the last image requested (see drawBackground())
	            return;
	        }

	        opt = opt || {};

	        var backgroundImage;
	        var backgroundSize = opt.size;
	        var backgroundRepeat = opt.repeat || 'no-repeat';
	        var backgroundOpacity = opt.opacity || 1;
	        var backgroundQuality = Math.abs(opt.quality) || 1;
	        var backgroundPattern = this.constructor.backgroundPatterns[camelCase(backgroundRepeat)];

	        if (isFunction(backgroundPattern)) {
	            // 'flip-x', 'flip-y', 'flip-xy', 'watermark' and custom
	            img.width *= backgroundQuality;
	            img.height *= backgroundQuality;
	            var canvas = backgroundPattern(img, opt);
	            if (!(canvas instanceof HTMLCanvasElement)) {
	                throw new Error('dia.Paper: background pattern must return an HTML Canvas instance');
	            }

	            backgroundImage = canvas.toDataURL('image/png');
	            backgroundRepeat = 'repeat';
	            if (isObject$1(backgroundSize)) {
	                // recalculate the tile size if an object passed in
	                backgroundSize.width *= canvas.width / img.width;
	                backgroundSize.height *= canvas.height / img.height;
	            } else if (backgroundSize === undefined) {
	                // calculate the tile size if no provided
	                opt.size = {
	                    width: canvas.width / backgroundQuality,
	                    height: canvas.height / backgroundQuality
	                };
	            }
	        } else {
	            // backgroundRepeat:
	            // no-repeat', 'round', 'space', 'repeat', 'repeat-x', 'repeat-y'
	            backgroundImage = img.src;
	            if (backgroundSize === undefined) {
	                // pass the image size for  the backgroundSize if no size provided
	                opt.size = {
	                    width: img.width,
	                    height: img.height
	                };
	            }
	        }

	        this.$background.css({
	            opacity: backgroundOpacity,
	            backgroundRepeat: backgroundRepeat,
	            backgroundImage: 'url(' + backgroundImage + ')'
	        });

	        this.updateBackgroundImage(opt);
	    },

	    updateBackgroundColor: function(color) {

	        this.$el.css('backgroundColor', color || '');
	    },

	    drawBackground: function(opt) {

	        opt = opt || {};

	        this.updateBackgroundColor(opt.color);

	        if (opt.image) {
	            opt = this._background = cloneDeep(opt);
	            guid(opt);
	            var img = document.createElement('img');
	            img.onload = this.drawBackgroundImage.bind(this, img, opt);
	            img.src = opt.image;
	        } else {
	            this.drawBackgroundImage(null);
	            this._background = null;
	        }

	        return this;
	    },

	    setInteractivity: function(value) {

	        this.options.interactive = value;

	        invoke(this._views, 'setInteractivity', value);
	    },

	    // Paper definitions.
	    // ------------------

	    isDefined: function(defId) {

	        return !!this.svg.getElementById(defId);
	    },

	    defineFilter: function(filter$1) {

	        if (!isObject$1(filter$1)) {
	            throw new TypeError('dia.Paper: defineFilter() requires 1. argument to be an object.');
	        }

	        var filterId = filter$1.id;
	        var name = filter$1.name;
	        // Generate a hash code from the stringified filter definition. This gives us
	        // a unique filter ID for different definitions.
	        if (!filterId) {
	            filterId = name + this.svg.id + hashCode(JSON.stringify(filter$1));
	        }
	        // If the filter already exists in the document,
	        // we're done and we can just use it (reference it using `url()`).
	        // If not, create one.
	        if (!this.isDefined(filterId)) {

	            var namespace = filter;
	            var filterSVGString = namespace[name] && namespace[name](filter$1.args || {});
	            if (!filterSVGString) {
	                throw new Error('Non-existing filter ' + name);
	            }

	            // Set the filter area to be 3x the bounding box of the cell
	            // and center the filter around the cell.
	            var filterAttrs = assign({
	                filterUnits: 'objectBoundingBox',
	                x: -1,
	                y: -1,
	                width: 3,
	                height: 3
	            }, filter$1.attrs, {
	                id: filterId
	            });

	            V(filterSVGString, filterAttrs).appendTo(this.defs);
	        }

	        return filterId;
	    },

	    defineGradient: function(gradient) {
	        if (!isObject$1(gradient)) {
	            throw new TypeError('dia.Paper: defineGradient() requires 1. argument to be an object.');
	        }
	        var ref = this;
	        var svg = ref.svg;
	        var defs = ref.defs;
	        var type = gradient.type;
	        var id = gradient.id; if ( id === void 0 ) id = type + svg.id + hashCode(JSON.stringify(gradient));
	        var stops = gradient.stops;
	        var attrs = gradient.attrs; if ( attrs === void 0 ) attrs = {};
	        // If the gradient already exists in the document,
	        // we're done and we can just use it (reference it using `url()`).
	        if (this.isDefined(id)) { return id; }
	        // If not, create one.
	        var stopVEls = toArray(stops).map(function (ref) {
	            var offset = ref.offset;
	            var color = ref.color;
	            var opacity = ref.opacity;

	            return V('stop').attr({
	                'offset': offset,
	                'stop-color': color,
	                'stop-opacity': Number.isFinite(opacity) ? opacity : 1
	            });
	        });
	        var gradientVEl = V(type, attrs, stopVEls);
	        gradientVEl.id = id;
	        gradientVEl.appendTo(defs);
	        return id;
	    },

	    definePattern: function(pattern) {
	        if (!isObject$1(pattern)) {
	            throw new TypeError('dia.Paper: definePattern() requires 1. argument to be an object.');
	        }
	        var ref = this;
	        var svg = ref.svg;
	        var defs = ref.defs;
	        var id = pattern.id; if ( id === void 0 ) id = svg.id + hashCode(JSON.stringify(pattern));
	        var markup = pattern.markup;
	        var attrs = pattern.attrs; if ( attrs === void 0 ) attrs = {};
	        if (!markup) {
	            throw new TypeError('dia.Paper: definePattern() requires markup.');
	        }
	        // If the gradient already exists in the document,
	        // we're done and we can just use it (reference it using `url()`).
	        if (this.isDefined(id)) { return id; }
	        // If not, create one.
	        var patternVEl = V('pattern', {
	            patternUnits: 'userSpaceOnUse'
	        });
	        patternVEl.id = id;
	        patternVEl.attr(attrs);
	        if (typeof markup === 'string') {
	            patternVEl.append(V(markup));
	        } else {
	            var ref$1 = parseDOMJSON(markup);
	            var fragment = ref$1.fragment;
	            patternVEl.append(fragment);
	        }
	        patternVEl.appendTo(defs);
	        return id;
	    },

	    defineMarker: function(marker) {
	        if (!isObject$1(marker)) {
	            throw new TypeError('dia.Paper: defineMarker() requires the first argument to be an object.');
	        }
	        var ref = this;
	        var svg = ref.svg;
	        var defs = ref.defs;
	        var id = marker.id; if ( id === void 0 ) id = svg.id + hashCode(JSON.stringify(marker));
	        var markup = marker.markup;
	        var attrs = marker.attrs; if ( attrs === void 0 ) attrs = {};
	        var markerUnits = marker.markerUnits; if ( markerUnits === void 0 ) markerUnits = 'userSpaceOnUse';
	        // If the marker already exists in the document,
	        // we're done and we can just use it (reference it using `url()`).
	        if (this.isDefined(id)) { return id; }
	        // If not, create one.
	        var markerVEl = V('marker', {
	            orient: 'auto',
	            overflow: 'visible',
	            markerUnits: markerUnits
	        });
	        markerVEl.id = id;
	        markerVEl.attr(attrs);
	        var markerContentVEl;
	        if (markup) {
	            var markupVEl;
	            if (typeof markup === 'string') {
	                // Marker object has a `markup` property of type string.
	                // - Construct V from the provided string.
	                markupVEl = V(markup);
	                // `markupVEl` is now either a single VEl, or an array of VEls.
	                // - Coerce it to an array.
	                markupVEl = (Array.isArray(markupVEl) ? markupVEl : [markupVEl]);
	            } else {
	                // Marker object has a `markup` property of type object.
	                // - Construct V from the object by parsing it as DOM JSON.
	                var ref$1 = parseDOMJSON(markup);
	                var fragment = ref$1.fragment;
	                markupVEl = V(fragment).children();
	            }
	            // `markupVEl` is an array with one or more VEls inside.
	            // - If there are multiple VEls, wrap them in a newly-constructed <g> element
	            if (markupVEl.length > 1) {
	                markerContentVEl = V('g').append(markupVEl);
	            } else {
	                markerContentVEl = markupVEl[0];
	            }
	        } else {
	            // Marker object is a flat structure.
	            // - Construct a new V of type `marker.type`.
	            var type = marker.type; if ( type === void 0 ) type = 'path';
	            markerContentVEl = V(type);
	        }
	        // `markerContentVEl` is a single VEl.
	        // Assign additional attributes to it (= context attributes + marker attributes):
	        // - Attribute values are taken from non-special properties of `marker`.
	        var markerAttrs = omit(marker, 'type', 'id', 'markup', 'attrs', 'markerUnits');
	        var markerAttrsKeys = Object.keys(markerAttrs);
	        markerAttrsKeys.forEach(function (key) {
	            var value = markerAttrs[key];
	            var markupValue = markerContentVEl.attr(key); // value coming from markupVEl (if any) = higher priority
	            if (markupValue == null) {
	                // Default logic:
	                markerContentVEl.attr(key, value);
	            } else {
	                // Properties with special logic should be added as cases to this switch block:
	                switch(key) {
	                    case 'transform':
	                        // - Prepend `transform` to existing value.
	                        markerContentVEl.attr(key, (value + ' ' + markupValue));
	                        break;
	                }
	            }
	        });
	        markerContentVEl.appendTo(markerVEl);
	        markerVEl.appendTo(defs);
	        return id;
	    }

	}, {

	    sorting: sortingTypes,

	    Layers: LayersNames,

	    backgroundPatterns: {

	        flipXy: function(img) {
	            // d b
	            // q p

	            var canvas = document.createElement('canvas');
	            var imgWidth = img.width;
	            var imgHeight = img.height;

	            canvas.width = 2 * imgWidth;
	            canvas.height = 2 * imgHeight;

	            var ctx = canvas.getContext('2d');
	            // top-left image
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
	            // xy-flipped bottom-right image
	            ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height);
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
	            // x-flipped top-right image
	            ctx.setTransform(-1, 0, 0, 1, canvas.width, 0);
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
	            // y-flipped bottom-left image
	            ctx.setTransform(1, 0, 0, -1, 0, canvas.height);
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);

	            return canvas;
	        },

	        flipX: function(img) {
	            // d b
	            // d b

	            var canvas = document.createElement('canvas');
	            var imgWidth = img.width;
	            var imgHeight = img.height;

	            canvas.width = imgWidth * 2;
	            canvas.height = imgHeight;

	            var ctx = canvas.getContext('2d');
	            // left image
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
	            // flipped right image
	            ctx.translate(2 * imgWidth, 0);
	            ctx.scale(-1, 1);
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);

	            return canvas;
	        },

	        flipY: function(img) {
	            // d d
	            // q q

	            var canvas = document.createElement('canvas');
	            var imgWidth = img.width;
	            var imgHeight = img.height;

	            canvas.width = imgWidth;
	            canvas.height = imgHeight * 2;

	            var ctx = canvas.getContext('2d');
	            // top image
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
	            // flipped bottom image
	            ctx.translate(0, 2 * imgHeight);
	            ctx.scale(1, -1);
	            ctx.drawImage(img, 0, 0, imgWidth, imgHeight);

	            return canvas;
	        },

	        watermark: function(img, opt) {
	            //   d
	            // d

	            opt = opt || {};

	            var imgWidth = img.width;
	            var imgHeight = img.height;

	            var canvas = document.createElement('canvas');
	            canvas.width = imgWidth * 3;
	            canvas.height = imgHeight * 3;

	            var ctx = canvas.getContext('2d');
	            var angle = isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20;
	            var radians = toRad(angle);
	            var stepX = canvas.width / 4;
	            var stepY = canvas.height / 4;

	            for (var i = 0; i < 4; i++) {
	                for (var j = 0; j < 4; j++) {
	                    if ((i + j) % 2 > 0) {
	                        // reset the current transformations
	                        ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY);
	                        ctx.rotate(radians);
	                        ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
	                    }
	                }
	            }

	            return canvas;
	        }
	    },

	    gridPatterns: {
	        dot: [{
	            color: '#AAAAAA',
	            thickness: 1,
	            markup: 'rect',
	            update: function(el, opt) {
	                V(el).attr({
	                    width: opt.thickness * opt.sx,
	                    height: opt.thickness * opt.sy,
	                    fill: opt.color
	                });
	            }
	        }],
	        fixedDot: [{
	            color: '#AAAAAA',
	            thickness: 1,
	            markup: 'rect',
	            update: function(el, opt) {
	                var size = opt.sx <= 1 ? opt.thickness * opt.sx : opt.thickness;
	                V(el).attr({ width: size, height: size, fill: opt.color });
	            }
	        }],
	        mesh: [{
	            color: '#AAAAAA',
	            thickness: 1,
	            markup: 'path',
	            update: function(el, opt) {

	                var d;
	                var width = opt.width;
	                var height = opt.height;
	                var thickness = opt.thickness;

	                if (width - thickness >= 0 && height - thickness >= 0) {
	                    d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
	                } else {
	                    d = 'M 0 0 0 0';
	                }

	                V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
	            }
	        }],
	        doubleMesh: [{
	            color: '#AAAAAA',
	            thickness: 1,
	            markup: 'path',
	            update: function(el, opt) {

	                var d;
	                var width = opt.width;
	                var height = opt.height;
	                var thickness = opt.thickness;

	                if (width - thickness >= 0 && height - thickness >= 0) {
	                    d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
	                } else {
	                    d = 'M 0 0 0 0';
	                }

	                V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
	            }
	        }, {
	            color: '#000000',
	            thickness: 3,
	            scaleFactor: 4,
	            markup: 'path',
	            update: function(el, opt) {

	                var d;
	                var width = opt.width;
	                var height = opt.height;
	                var thickness = opt.thickness;

	                if (width - thickness >= 0 && height - thickness >= 0) {
	                    d = ['M', width, 0, 'H0 M0 0 V0', height].join(' ');
	                } else {
	                    d = 'M 0 0 0 0';
	                }

	                V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness });
	            }
	        }]
	    }
	});

	var ToolView = View.extend({
	    name: null,
	    tagName: 'g',
	    className: 'tool',
	    svgElement: true,
	    _visible: true,

	    init: function() {
	        var name = this.name;
	        if (name) { this.vel.attr('data-tool-name', name); }
	    },

	    configure: function(view, toolsView) {
	        this.relatedView = view;
	        this.paper = view.paper;
	        this.parentView = toolsView;
	        this.simulateRelatedView(this.el);
	        // Delegate events in case the ToolView was removed from the DOM and reused.
	        this.delegateEvents();
	        return this;
	    },

	    simulateRelatedView: function(el) {
	        if (el) { el.setAttribute('model-id', this.relatedView.model.id); }
	    },

	    getName: function() {
	        return this.name;
	    },

	    show: function() {
	        this.el.style.display = '';
	        this._visible = true;
	    },

	    hide: function() {
	        this.el.style.display = 'none';
	        this._visible = false;
	    },

	    isVisible: function() {
	        return !!this._visible;
	    },

	    focus: function() {
	        var opacity = this.options.focusOpacity;
	        if (isFinite(opacity)) { this.el.style.opacity = opacity; }
	        this.parentView.focusTool(this);
	    },

	    blur: function() {
	        this.el.style.opacity = '';
	        this.parentView.blurTool(this);
	    },

	    update: function() {
	        // to be overridden
	    },

	    guard: function(evt) {
	        // Let the context-menu event bubble up to the relatedView
	        var ref = this;
	        var paper = ref.paper;
	        var relatedView = ref.relatedView;
	        if (!paper || !relatedView) { return true; }
	        return paper.guard(evt, relatedView);
	    }
	});

	var ToolsView = View.extend({
	    tagName: 'g',
	    className: 'tools',
	    svgElement: true,
	    tools: null,
	    isRendered: false,
	    options: {
	        tools: null,
	        relatedView: null,
	        name: null,
	        // layer?: LayersNames.TOOLS
	        // z?: number
	    },

	    configure: function(options) {
	        options = assign(this.options, options);
	        var tools = options.tools;
	        if (!Array.isArray(tools)) { return this; }
	        var relatedView = options.relatedView;
	        if (!(relatedView instanceof CellView)) { return this; }
	        var views = this.tools = [];
	        for (var i = 0, n = tools.length; i < n; i++) {
	            var tool = tools[i];
	            if (!(tool instanceof ToolView)) { continue; }
	            tool.configure(relatedView, this);
	            this.vel.append(tool.el);
	            views.push(tool);
	        }
	        this.isRendered = false;
	        relatedView.requestUpdate(relatedView.getFlag('TOOLS'));
	        return this;
	    },

	    getName: function() {
	        return this.options.name;
	    },

	    update: function(opt) {

	        opt || (opt = {});
	        var tools = this.tools;
	        if (!tools) { return this; }
	        var isRendered = this.isRendered;
	        for (var i = 0, n = tools.length; i < n; i++) {
	            var tool = tools[i];
	            if (!isRendered) {
	                // First update executes render()
	                tool.render();
	            } else if (opt.tool !== tool.cid && tool.isVisible()) {
	                tool.update();
	            }
	        }
	        if (!this.isMounted()) {
	            this.mount();
	        }
	        if (!isRendered) {
	            // Make sure tools are visible (if they were hidden and the tool removed)
	            this.blurTool();
	            this.isRendered = true;
	        }
	        return this;
	    },

	    focusTool: function(focusedTool) {

	        var tools = this.tools;
	        if (!tools) { return this; }
	        for (var i = 0, n = tools.length; i < n; i++) {
	            var tool = tools[i];
	            if (focusedTool === tool) {
	                tool.show();
	            } else {
	                tool.hide();
	            }
	        }
	        return this;
	    },

	    blurTool: function(blurredTool) {
	        var tools = this.tools;
	        if (!tools) { return this; }
	        for (var i = 0, n = tools.length; i < n; i++) {
	            var tool = tools[i];
	            if (tool !== blurredTool && !tool.isVisible()) {
	                tool.show();
	                tool.update();
	            }
	        }
	        return this;
	    },

	    hide: function() {
	        return this.focusTool(null);
	    },

	    show: function() {
	        return this.blurTool(null);
	    },

	    onRemove: function() {
	        var tools = this.tools;
	        if (!tools) { return this; }
	        for (var i = 0, n = tools.length; i < n; i++) {
	            tools[i].remove();
	        }
	        this.tools = null;
	    },

	    mount: function() {
	        var ref = this;
	        var options = ref.options;
	        var el = ref.el;
	        var relatedView = options.relatedView;
	        var layer = options.layer; if ( layer === void 0 ) layer = LayersNames.TOOLS;
	        var z = options.z;
	        if (relatedView) {
	            if (layer) {
	                relatedView.paper.getLayerView(layer).insertSortedNode(el, z);
	            } else {
	                relatedView.el.appendChild(el);
	            }
	        }
	        return this;
	    },

	    isMounted: function() {
	        return this.el.parentNode !== null;
	    }

	});



	var index$2 = ({
		Graph: Graph,
		attributes: attributes,
		LayersNames: LayersNames,
		PaperLayer: PaperLayer,
		Cell: Cell,
		CellView: CellView,
		Element: Element$1,
		ElementView: ElementView,
		Link: Link,
		LinkView: LinkView,
		Paper: Paper,
		ToolView: ToolView,
		ToolsView: ToolsView,
		HighlighterView: HighlighterView
	});

	var DirectedGraph = {

	    exportElement: function(element) {

	        // The width and height of the element.
	        return element.size();
	    },

	    exportLink: function(link) {

	        var labelSize = link.get('labelSize') || {};
	        var edge = {
	            // The number of ranks to keep between the source and target of the edge.
	            minLen: link.get('minLen') || 1,
	            // The weight to assign edges. Higher weight edges are generally
	            // made shorter and straighter than lower weight edges.
	            weight: link.get('weight') || 1,
	            // Where to place the label relative to the edge.
	            // l = left, c = center r = right.
	            labelpos: link.get('labelPosition') || 'c',
	            // How many pixels to move the label away from the edge.
	            // Applies only when labelpos is l or r.
	            labeloffset: link.get('labelOffset') || 0,
	            // The width of the edge label in pixels.
	            width: labelSize.width || 0,
	            // The height of the edge label in pixels.
	            height: labelSize.height || 0
	        };

	        return edge;
	    },

	    importElement: function(opt, v, gl) {

	        var element = this.getCell(v);
	        var glNode = gl.node(v);

	        if (opt.setPosition) {
	            opt.setPosition(element, glNode);
	        } else {
	            element.set('position', {
	                x: glNode.x - glNode.width / 2,
	                y: glNode.y - glNode.height / 2
	            });
	        }
	    },

	    importLink: function(opt, edgeObj, gl) {

	        var SIMPLIFY_THRESHOLD = 0.001;

	        var link = this.getCell(edgeObj.name);
	        var glEdge = gl.edge(edgeObj);
	        var points = glEdge.points || [];
	        var polyline = new Polyline(points);

	        // check the `setLinkVertices` here for backwards compatibility
	        if (opt.setVertices || opt.setLinkVertices) {
	            if (isFunction(opt.setVertices)) {
	                opt.setVertices(link, points);
	            } else {
	                // simplify the `points` polyline
	                polyline.simplify({ threshold: SIMPLIFY_THRESHOLD });
	                var polylinePoints = polyline.points.map(function (point) { return (point.toJSON()); }); // JSON of points after simplification
	                var numPolylinePoints = polylinePoints.length; // number of points after simplification
	                // set simplified polyline points as link vertices
	                // remove first and last polyline points (= source/target sonnectionPoints)
	                link.set('vertices', polylinePoints.slice(1, numPolylinePoints - 1));
	            }
	        }

	        if (opt.setLabels && ('x' in glEdge) && ('y' in glEdge)) {
	            var labelPosition = { x: glEdge.x, y: glEdge.y };
	            if (isFunction(opt.setLabels)) {
	                opt.setLabels(link, labelPosition, points);
	            } else {
	                // convert the absolute label position to a relative position
	                // towards the closest point on the edge
	                var length = polyline.closestPointLength(labelPosition);
	                var closestPoint = polyline.pointAtLength(length);
	                var distance = (length / polyline.length());
	                var offset = new Point(labelPosition).difference(closestPoint).toJSON();
	                link.label(0, {
	                    position: {
	                        distance: distance,
	                        offset: offset
	                    }
	                });
	            }
	        }
	    },

	    layout: function(graphOrCells, opt) {

	        var graph;

	        if (graphOrCells instanceof Graph) {
	            graph = graphOrCells;
	        } else {
	            // Reset cells in dry mode so the graph reference is not stored on the cells.
	            // `sort: false` to prevent elements to change their order based on the z-index
	            graph = (new Graph()).resetCells(graphOrCells, { dry: true, sort: false });
	        }

	        // This is not needed anymore.
	        graphOrCells = null;

	        opt = defaults(opt || {}, {
	            resizeClusters: true,
	            clusterPadding: 10,
	            exportElement: this.exportElement,
	            exportLink: this.exportLink
	        });

	        /* eslint-disable no-undef */
	        var dagreUtil = opt.dagre || (typeof dagre !== 'undefined' ? dagre : undefined);
	        /* eslint-enable no-undef */

	        if (dagreUtil === undefined) { throw new Error('The the "dagre" utility is a mandatory dependency.'); }

	        // create a graphlib.Graph that represents the joint.dia.Graph
	        // var glGraph = graph.toGraphLib({
	        var glGraph = DirectedGraph.toGraphLib(graph, {
	            graphlib: opt.graphlib,
	            directed: true,
	            // We are about to use edge naming feature.
	            multigraph: true,
	            // We are able to layout graphs with embeds.
	            compound: true,
	            setNodeLabel: opt.exportElement,
	            setEdgeLabel: opt.exportLink,
	            setEdgeName: function(link) {
	                // Graphlib edges have no ids. We use edge name property
	                // to store and retrieve ids instead.
	                return link.id;
	            }
	        });

	        var glLabel = {};
	        var marginX = opt.marginX || 0;
	        var marginY = opt.marginY || 0;

	        // Dagre layout accepts options as lower case.
	        // Direction for rank nodes. Can be TB, BT, LR, or RL
	        if (opt.rankDir) { glLabel.rankdir = opt.rankDir; }
	        // Alignment for rank nodes. Can be UL, UR, DL, or DR
	        if (opt.align) { glLabel.align = opt.align; }
	        // Number of pixels that separate nodes horizontally in the layout.
	        if (opt.nodeSep) { glLabel.nodesep = opt.nodeSep; }
	        // Number of pixels that separate edges horizontally in the layout.
	        if (opt.edgeSep) { glLabel.edgesep = opt.edgeSep; }
	        // Number of pixels between each rank in the layout.
	        if (opt.rankSep) { glLabel.ranksep = opt.rankSep; }
	        // Type of algorithm to assign a rank to each node in the input graph.
	        // Possible values: network-simplex, tight-tree or longest-path
	        if (opt.ranker) { glLabel.ranker = opt.ranker; }
	        // Number of pixels to use as a margin around the left and right of the graph.
	        if (marginX) { glLabel.marginx = marginX; }
	        // Number of pixels to use as a margin around the top and bottom of the graph.
	        if (marginY) { glLabel.marginy = marginY; }

	        // Set the option object for the graph label.
	        glGraph.setGraph(glLabel);

	        // Executes the layout.
	        dagreUtil.layout(glGraph, { debugTiming: !!opt.debugTiming });

	        // Wrap all graph changes into a batch.
	        graph.startBatch('layout');

	        DirectedGraph.fromGraphLib(glGraph, {
	            importNode: this.importElement.bind(graph, opt),
	            importEdge: this.importLink.bind(graph, opt)
	        });

	        // // Update the graph.
	        // graph.fromGraphLib(glGraph, {
	        //     importNode: this.importElement.bind(graph, opt),
	        //     importEdge: this.importLink.bind(graph, opt)
	        // });

	        if (opt.resizeClusters) {
	            // Resize and reposition cluster elements (parents of other elements)
	            // to fit their children.
	            // 1. filter clusters only
	            // 2. map id on cells
	            // 3. sort cells by their depth (the deepest first)
	            // 4. resize cell to fit their direct children only.
	            var clusters = glGraph.nodes()
	                .filter(function(v) { return glGraph.children(v).length > 0; })
	                .map(graph.getCell.bind(graph))
	                .sort(function(aCluster, bCluster) {
	                    return bCluster.getAncestors().length - aCluster.getAncestors().length;
	                });

	            invoke(clusters, 'fitToChildren', { padding: opt.clusterPadding });
	        }

	        graph.stopBatch('layout');

	        // Width and height of the graph extended by margins.
	        var glSize = glGraph.graph();
	        // Return the bounding box of the graph after the layout.
	        return new Rect(
	            marginX,
	            marginY,
	            Math.abs(glSize.width - 2 * marginX),
	            Math.abs(glSize.height - 2 * marginY)
	        );
	    },

	    fromGraphLib: function(glGraph, opt) {

	        opt = opt || {};

	        var importNode = opt.importNode || noop;
	        var importEdge = opt.importEdge || noop;
	        var graph = (this instanceof Graph) ? this : new Graph;

	        // Import all nodes.
	        glGraph.nodes().forEach(function(node) {
	            importNode.call(graph, node, glGraph, graph, opt);
	        });

	        // Import all edges.
	        glGraph.edges().forEach(function(edge) {
	            importEdge.call(graph, edge, glGraph, graph, opt);
	        });

	        return graph;
	    },

	    // Create new graphlib graph from existing JointJS graph.
	    toGraphLib: function(graph, opt) {

	        opt = opt || {};

	        /* eslint-disable no-undef */
	        var graphlibUtil = opt.graphlib || (typeof graphlib !== 'undefined' ? graphlib : undefined);
	        /* eslint-enable no-undef */

	        if (graphlibUtil === undefined) { throw new Error('The the "graphlib" utility is a mandatory dependency.'); }

	        var glGraphType = pick(opt, 'directed', 'compound', 'multigraph');
	        var glGraph = new graphlibUtil.Graph(glGraphType);
	        var setNodeLabel = opt.setNodeLabel || noop;
	        var setEdgeLabel = opt.setEdgeLabel || noop;
	        var setEdgeName = opt.setEdgeName || noop;
	        var collection = graph.get('cells');

	        for (var i = 0, n = collection.length; i < n; i++) {

	            var cell = collection.at(i);
	            if (cell.isLink()) {

	                var source = cell.get('source');
	                var target = cell.get('target');

	                // Links that end at a point are ignored.
	                if (!source.id || !target.id) { break; }

	                // Note that if we are creating a multigraph we can name the edges. If
	                // we try to name edges on a non-multigraph an exception is thrown.
	                glGraph.setEdge(source.id, target.id, setEdgeLabel(cell), setEdgeName(cell));

	            } else {

	                glGraph.setNode(cell.id, setNodeLabel(cell));

	                // For the compound graphs we have to take embeds into account.
	                if (glGraph.isCompound() && cell.has('parent')) {
	                    var parentId = cell.get('parent');
	                    if (collection.has(parentId)) {
	                        // Make sure the parent cell is included in the graph (this can
	                        // happen when the layout is run on part of the graph only).
	                        glGraph.setParent(cell.id, parentId);
	                    }
	                }
	            }
	        }

	        return glGraph;
	    }
	};

	Graph.prototype.toGraphLib = function(opt) {

	    return DirectedGraph.toGraphLib(this, opt);
	};

	Graph.prototype.fromGraphLib = function(glGraph, opt) {

	    return DirectedGraph.fromGraphLib.call(this, glGraph, opt);
	};

	var env = {

	    _results: {},

	    _tests: {

	        svgforeignobject: function() {
	            return !!document.createElementNS &&
	                /SVGForeignObject/.test(({}).toString.call(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')));
	        }
	    },

	    addTest: function(name, fn) {

	        return this._tests[name] = fn;
	    },

	    test: function(name) {

	        var fn = this._tests[name];

	        if (!fn) {
	            throw new Error('Test not defined ("' + name + '"). Use `joint.env.addTest(name, fn) to add a new test.`');
	        }

	        var result = this._results[name];

	        if (typeof result !== 'undefined') {
	            return result;
	        }

	        try {
	            result = fn();
	        } catch (error) {
	            result = false;
	        }

	        // Cache the test result.
	        this._results[name] = result;

	        return result;
	    }
	};

	var Generic = Element$1.define('basic.Generic', {
	    attrs: {
	        '.': { fill: '#ffffff', stroke: 'none' }
	    }
	});

	var Rect$1 = Generic.define('basic.Rect', {
	    attrs: {
	        'rect': {
	            fill: '#ffffff',
	            stroke: '#000000',
	            width: 100,
	            height: 60
	        },
	        'text': {
	            fill: '#000000',
	            text: '',
	            'font-size': 14,
	            'ref-x': .5,
	            'ref-y': .5,
	            'text-anchor': 'middle',
	            'y-alignment': 'middle',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><rect/></g><text/></g>'
	});

	var TextView = ElementView.extend({

	    presentationAttributes: ElementView.addPresentationAttributes({
	        // The element view is not automatically re-scaled to fit the model size
	        // when the attribute 'attrs' is changed.
	        attrs: ['SCALE']
	    }),

	    confirmUpdate: function() {
	        var flags = ElementView.prototype.confirmUpdate.apply(this, arguments);
	        if (this.hasFlag(flags, 'SCALE')) {
	            this.resize();
	            flags = this.removeFlag(flags, 'SCALE');
	        }
	        return flags;
	    }
	});

	var Text = Generic.define('basic.Text', {
	    attrs: {
	        'text': {
	            'font-size': 18,
	            fill: '#000000'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><text/></g></g>',
	});

	var Circle = Generic.define('basic.Circle', {
	    size: { width: 60, height: 60 },
	    attrs: {
	        'circle': {
	            fill: '#ffffff',
	            stroke: '#000000',
	            r: 30,
	            cx: 30,
	            cy: 30
	        },
	        'text': {
	            'font-size': 14,
	            text: '',
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-y': .5,
	            'y-alignment': 'middle',
	            fill: '#000000',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><circle/></g><text/></g>',
	});

	var Ellipse$1 = Generic.define('basic.Ellipse', {
	    size: { width: 60, height: 40 },
	    attrs: {
	        'ellipse': {
	            fill: '#ffffff',
	            stroke: '#000000',
	            rx: 30,
	            ry: 20,
	            cx: 30,
	            cy: 20
	        },
	        'text': {
	            'font-size': 14,
	            text: '',
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-y': .5,
	            'y-alignment': 'middle',
	            fill: '#000000',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><ellipse/></g><text/></g>',
	});

	var Polygon$1 = Generic.define('basic.Polygon', {
	    size: { width: 60, height: 40 },
	    attrs: {
	        'polygon': {
	            fill: '#ffffff',
	            stroke: '#000000'
	        },
	        'text': {
	            'font-size': 14,
	            text: '',
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-dy': 20,
	            'y-alignment': 'middle',
	            fill: '#000000',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><polygon/></g><text/></g>',
	});

	var Polyline$1 = Generic.define('basic.Polyline', {
	    size: { width: 60, height: 40 },
	    attrs: {
	        'polyline': {
	            fill: '#ffffff',
	            stroke: '#000000'
	        },
	        'text': {
	            'font-size': 14,
	            text: '',
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-dy': 20,
	            'y-alignment': 'middle',
	            fill: '#000000',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><polyline/></g><text/></g>',
	});

	var Image = Generic.define('basic.Image', {
	    attrs: {
	        'text': {
	            'font-size': 14,
	            text: '',
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-dy': 20,
	            'y-alignment': 'middle',
	            fill: '#000000',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><image/></g><text/></g>',
	});

	var Path$1 = Generic.define('basic.Path', {
	    size: { width: 60, height: 60 },
	    attrs: {
	        'path': {
	            fill: '#ffffff',
	            stroke: '#000000'
	        },
	        'text': {
	            'font-size': 14,
	            text: '',
	            'text-anchor': 'middle',
	            'ref': 'path',
	            'ref-x': .5,
	            'ref-dy': 10,
	            fill: '#000000',
	            'font-family': 'Arial, helvetica, sans-serif'
	        }
	    }

	}, {
	    markup: '<g class="rotatable"><g class="scalable"><path/></g><text/></g>',
	});

	var Rhombus = Path$1.define('basic.Rhombus', {
	    attrs: {
	        'path': {
	            d: 'M 30 0 L 60 30 30 60 0 30 z'
	        },
	        'text': {
	            'ref-y': .5,
	            'ref-dy': null,
	            'y-alignment': 'middle'
	        }
	    }
	});

	var svgForeignObjectSupported = env.test('svgforeignobject');

	var TextBlock = Generic.define('basic.TextBlock', {
	    // see joint.css for more element styles
	    attrs: {
	        rect: {
	            fill: '#ffffff',
	            stroke: '#000000',
	            width: 80,
	            height: 100
	        },
	        text: {
	            fill: '#000000',
	            'font-size': 14,
	            'font-family': 'Arial, helvetica, sans-serif'
	        },
	        '.content': {
	            text: '',
	            'ref-x': .5,
	            'ref-y': .5,
	            'y-alignment': 'middle',
	            'x-alignment': 'middle'
	        }
	    },

	    content: ''
	}, {
	    markup: [
	        '<g class="rotatable">',
	        '<g class="scalable"><rect/></g>',
	        svgForeignObjectSupported
	            ? '<foreignObject class="fobj"><body xmlns="http://www.w3.org/1999/xhtml"><div class="content"/></body></foreignObject>'
	            : '<text class="content"/>',
	        '</g>'
	    ].join(''),

	    initialize: function() {

	        this.listenTo(this, 'change:size', this.updateSize);
	        this.listenTo(this, 'change:content', this.updateContent);
	        this.updateSize(this, this.get('size'));
	        this.updateContent(this, this.get('content'));
	        Generic.prototype.initialize.apply(this, arguments);
	    },

	    updateSize: function(cell, size) {

	        // Selector `foreignObject' doesn't work across all browsers, we're using class selector instead.
	        // We have to clone size as we don't want attributes.div.style to be same object as attributes.size.
	        this.attr({
	            '.fobj': assign({}, size),
	            div: {
	                style: assign({}, size)
	            }
	        });
	    },

	    updateContent: function(cell, content) {

	        if (svgForeignObjectSupported) {

	            // Content element is a <div> element.
	            this.attr({
	                '.content': {
	                    html: sanitizeHTML(content)
	                }
	            });

	        } else {

	            // Content element is a <text> element.
	            // SVG elements don't have innerHTML attribute.
	            this.attr({
	                '.content': {
	                    text: content
	                }
	            });
	        }
	    },

	    // Here for backwards compatibility:
	    setForeignObjectSize: function() {

	        this.updateSize.apply(this, arguments);
	    },

	    // Here for backwards compatibility:
	    setDivContent: function() {

	        this.updateContent.apply(this, arguments);
	    }
	});

	// TextBlockView implements the fallback for IE when no foreignObject exists and
	// the text needs to be manually broken.
	var TextBlockView = ElementView.extend({

	    presentationAttributes: svgForeignObjectSupported
	        ? ElementView.prototype.presentationAttributes
	        : ElementView.addPresentationAttributes({
	            content: ['CONTENT'],
	            size: ['CONTENT']
	        }),

	    initFlag: ['RENDER', 'CONTENT'],

	    confirmUpdate: function() {
	        var flags = ElementView.prototype.confirmUpdate.apply(this, arguments);
	        if (this.hasFlag(flags, 'CONTENT')) {
	            this.updateContent(this.model);
	            flags = this.removeFlag(flags, 'CONTENT');
	        }
	        return flags;
	    },

	    update: function(_, renderingOnlyAttrs) {

	        var model = this.model;

	        if (!svgForeignObjectSupported) {

	            // Update everything but the content first.
	            var noTextAttrs = omit(renderingOnlyAttrs || model.get('attrs'), '.content');
	            ElementView.prototype.update.call(this, model, noTextAttrs);

	            if (!renderingOnlyAttrs || has$2(renderingOnlyAttrs, '.content')) {
	                // Update the content itself.
	                this.updateContent(model, renderingOnlyAttrs);
	            }

	        } else {

	            ElementView.prototype.update.call(this, model, renderingOnlyAttrs);
	        }
	    },

	    updateContent: function(cell, renderingOnlyAttrs) {

	        // Create copy of the text attributes
	        var textAttrs = merge({}, (renderingOnlyAttrs || cell.get('attrs'))['.content']);

	        textAttrs = omit(textAttrs, 'text');

	        // Break the content to fit the element size taking into account the attributes
	        // set on the model.
	        var text = breakText(cell.get('content'), cell.get('size'), textAttrs, {
	            // measuring sandbox svg document
	            svgDocument: this.paper.svg
	        });

	        // Create a new attrs with same structure as the model attrs { text: { *textAttributes* }}
	        var attrs = setByPath({}, '.content', textAttrs, '/');

	        // Replace text attribute with the one we just processed.
	        attrs['.content'].text = text;

	        // Update the view using renderingOnlyAttributes parameter.
	        ElementView.prototype.update.call(this, cell, attrs);
	    }
	});

	var basic = ({
		Generic: Generic,
		Rect: Rect$1,
		TextView: TextView,
		Text: Text,
		Circle: Circle,
		Ellipse: Ellipse$1,
		Polygon: Polygon$1,
		Polyline: Polyline$1,
		Image: Image,
		Path: Path$1,
		Rhombus: Rhombus,
		TextBlock: TextBlock,
		TextBlockView: TextBlockView
	});

	// ELEMENTS

	var Rectangle = Element$1.define('standard.Rectangle', {
	    attrs: {
	        body: {
	            refWidth: '100%',
	            refHeight: '100%',
	            strokeWidth: 2,
	            stroke: '#000000',
	            fill: '#FFFFFF'
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'rect',
	        selector: 'body',
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var Circle$1 = Element$1.define('standard.Circle', {
	    attrs: {
	        body: {
	            refCx: '50%',
	            refCy: '50%',
	            refR: '50%',
	            strokeWidth: 2,
	            stroke: '#333333',
	            fill: '#FFFFFF'
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'circle',
	        selector: 'body'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var Ellipse$2 = Element$1.define('standard.Ellipse', {
	    attrs: {
	        body: {
	            refCx: '50%',
	            refCy: '50%',
	            refRx: '50%',
	            refRy: '50%',
	            strokeWidth: 2,
	            stroke: '#333333',
	            fill: '#FFFFFF'
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'ellipse',
	        selector: 'body'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var Path$2 = Element$1.define('standard.Path', {
	    attrs: {
	        body: {
	            refD: 'M 0 0 L 10 0 10 10 0 10 Z',
	            strokeWidth: 2,
	            stroke: '#333333',
	            fill: '#FFFFFF'
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'path',
	        selector: 'body'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var Polygon$2 = Element$1.define('standard.Polygon', {
	    attrs: {
	        body: {
	            refPoints: '0 0 10 0 10 10 0 10',
	            strokeWidth: 2,
	            stroke: '#333333',
	            fill: '#FFFFFF'
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'polygon',
	        selector: 'body'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var Polyline$2 = Element$1.define('standard.Polyline', {
	    attrs: {
	        body: {
	            refPoints: '0 0 10 0 10 10 0 10 0 0',
	            strokeWidth: 2,
	            stroke: '#333333',
	            fill: '#FFFFFF'
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'polyline',
	        selector: 'body'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var Image$1 = Element$1.define('standard.Image', {
	    attrs: {
	        image: {
	            refWidth: '100%',
	            refHeight: '100%',
	            // xlinkHref: '[URL]'
	        },
	        label: {
	            textVerticalAnchor: 'top',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '100%',
	            refY2: 10,
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'image',
	        selector: 'image'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var BorderedImage = Element$1.define('standard.BorderedImage', {
	    attrs: {
	        border: {
	            refWidth: '100%',
	            refHeight: '100%',
	            stroke: '#333333',
	            strokeWidth: 2
	        },
	        background: {
	            refWidth: -1,
	            refHeight: -1,
	            x: 0.5,
	            y: 0.5,
	            fill: '#FFFFFF'
	        },
	        image: {
	            // xlinkHref: '[URL]'
	            refWidth: -1,
	            refHeight: -1,
	            x: 0.5,
	            y: 0.5
	        },
	        label: {
	            textVerticalAnchor: 'top',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '100%',
	            refY2: 10,
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'rect',
	        selector: 'background',
	        attributes: {
	            'stroke': 'none'
	        }
	    }, {
	        tagName: 'image',
	        selector: 'image'
	    }, {
	        tagName: 'rect',
	        selector: 'border',
	        attributes: {
	            'fill': 'none'
	        }
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var EmbeddedImage = Element$1.define('standard.EmbeddedImage', {
	    attrs: {
	        body: {
	            refWidth: '100%',
	            refHeight: '100%',
	            stroke: '#333333',
	            fill: '#FFFFFF',
	            strokeWidth: 2
	        },
	        image: {
	            // xlinkHref: '[URL]'
	            refWidth: '30%',
	            refHeight: -20,
	            x: 10,
	            y: 10,
	            preserveAspectRatio: 'xMidYMin'
	        },
	        label: {
	            textVerticalAnchor: 'top',
	            textAnchor: 'left',
	            refX: '30%',
	            refX2: 20, // 10 + 10
	            refY: 10,
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'rect',
	        selector: 'body'
	    }, {
	        tagName: 'image',
	        selector: 'image'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var InscribedImage = Element$1.define('standard.InscribedImage', {
	    attrs: {
	        border: {
	            refRx: '50%',
	            refRy: '50%',
	            refCx: '50%',
	            refCy: '50%',
	            stroke: '#333333',
	            strokeWidth: 2
	        },
	        background: {
	            refRx: '50%',
	            refRy: '50%',
	            refCx: '50%',
	            refCy: '50%',
	            fill: '#FFFFFF'
	        },
	        image: {
	            // The image corners touch the border when its size is Math.sqrt(2) / 2 = 0.707.. ~= 70%
	            refWidth: '68%',
	            refHeight: '68%',
	            // The image offset is calculated as (100% - 68%) / 2
	            refX: '16%',
	            refY: '16%',
	            preserveAspectRatio: 'xMidYMid'
	            // xlinkHref: '[URL]'
	        },
	        label: {
	            textVerticalAnchor: 'top',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '100%',
	            refY2: 10,
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'ellipse',
	        selector: 'background'
	    }, {
	        tagName: 'image',
	        selector: 'image'
	    }, {
	        tagName: 'ellipse',
	        selector: 'border',
	        attributes: {
	            'fill': 'none'
	        }
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }]
	});

	var HeaderedRectangle = Element$1.define('standard.HeaderedRectangle', {
	    attrs: {
	        body: {
	            refWidth: '100%',
	            refHeight: '100%',
	            strokeWidth: 2,
	            stroke: '#000000',
	            fill: '#FFFFFF'
	        },
	        header: {
	            refWidth: '100%',
	            height: 30,
	            strokeWidth: 2,
	            stroke: '#000000',
	            fill: '#FFFFFF'
	        },
	        headerText: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: 15,
	            fontSize: 16,
	            fill: '#333333'
	        },
	        bodyText: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '50%',
	            refY2: 15,
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'rect',
	        selector: 'body'
	    }, {
	        tagName: 'rect',
	        selector: 'header'
	    }, {
	        tagName: 'text',
	        selector: 'headerText'
	    }, {
	        tagName: 'text',
	        selector: 'bodyText'
	    }]
	});

	var CYLINDER_TILT = 10;

	var Cylinder = Element$1.define('standard.Cylinder', {
	    attrs: {
	        body: {
	            lateralArea: CYLINDER_TILT,
	            fill: '#FFFFFF',
	            stroke: '#333333',
	            strokeWidth: 2
	        },
	        top: {
	            refCx: '50%',
	            cy: CYLINDER_TILT,
	            refRx: '50%',
	            ry: CYLINDER_TILT,
	            fill: '#FFFFFF',
	            stroke: '#333333',
	            strokeWidth: 2
	        },
	        label: {
	            textVerticalAnchor: 'middle',
	            textAnchor: 'middle',
	            refX: '50%',
	            refY: '100%',
	            refY2: 15,
	            fontSize: 14,
	            fill: '#333333'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'path',
	        selector: 'body'
	    }, {
	        tagName: 'ellipse',
	        selector: 'top'
	    }, {
	        tagName: 'text',
	        selector: 'label'
	    }],

	    topRy: function(t, opt) {
	        // getter
	        if (t === undefined) { return this.attr('body/lateralArea'); }

	        // setter
	        var isPercentageSetter = isPercentage(t);

	        var bodyAttrs = { lateralArea: t };
	        var topAttrs = isPercentageSetter
	            ? { refCy: t, refRy: t, cy: null, ry: null }
	            : { refCy: null, refRy: null, cy: t, ry: t };

	        return this.attr({ body: bodyAttrs, top: topAttrs }, opt);
	    }

	}, {
	    attributes: {
	        lateralArea: {
	            set: function(t, refBBox) {
	                var isPercentageSetter = isPercentage(t);
	                if (isPercentageSetter) { t = parseFloat(t) / 100; }

	                var x = refBBox.x;
	                var y = refBBox.y;
	                var w = refBBox.width;
	                var h = refBBox.height;

	                // curve control point variables
	                var rx = w / 2;
	                var ry = isPercentageSetter ? (h * t) : t;

	                var kappa = V.KAPPA;
	                var cx = kappa * rx;
	                var cy = kappa * (isPercentageSetter ? (h * t) : t);

	                // shape variables
	                var xLeft = x;
	                var xCenter = x + (w / 2);
	                var xRight = x + w;

	                var ySideTop = y + ry;
	                var yCurveTop = ySideTop - ry;
	                var ySideBottom = y + h - ry;
	                var yCurveBottom = y + h;

	                // return calculated shape
	                var data = [
	                    'M', xLeft, ySideTop,
	                    'L', xLeft, ySideBottom,
	                    'C', x, (ySideBottom + cy), (xCenter - cx), yCurveBottom, xCenter, yCurveBottom,
	                    'C', (xCenter + cx), yCurveBottom, xRight, (ySideBottom + cy), xRight, ySideBottom,
	                    'L', xRight, ySideTop,
	                    'C', xRight, (ySideTop - cy), (xCenter + cx), yCurveTop, xCenter, yCurveTop,
	                    'C', (xCenter - cx), yCurveTop, xLeft, (ySideTop - cy), xLeft, ySideTop,
	                    'Z'
	                ];
	                return { d: data.join(' ') };
	            }
	        }
	    }
	});

	var foLabelMarkup = {
	    tagName: 'foreignObject',
	    selector: 'foreignObject',
	    attributes: {
	        'overflow': 'hidden'
	    },
	    children: [{
	        tagName: 'div',
	        namespaceURI: 'http://www.w3.org/1999/xhtml',
	        selector: 'label',
	        style: {
	            width: '100%',
	            height: '100%',
	            position: 'static',
	            backgroundColor: 'transparent',
	            textAlign: 'center',
	            margin: 0,
	            padding: '0px 5px',
	            boxSizing: 'border-box',
	            display: 'flex',
	            alignItems: 'center',
	            justifyContent: 'center'
	        }
	    }]
	};

	var svgLabelMarkup = {
	    tagName: 'text',
	    selector: 'label',
	    attributes: {
	        'text-anchor': 'middle'
	    }
	};

	var labelMarkup = (env.test('svgforeignobject')) ? foLabelMarkup : svgLabelMarkup;

	var TextBlock$1 = Element$1.define('standard.TextBlock', {
	    attrs: {
	        body: {
	            refWidth: '100%',
	            refHeight: '100%',
	            stroke: '#333333',
	            fill: '#ffffff',
	            strokeWidth: 2
	        },
	        foreignObject: {
	            refWidth: '100%',
	            refHeight: '100%'
	        },
	        label: {
	            style: {
	                fontSize: 14
	            }
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'rect',
	        selector: 'body'
	    }, labelMarkup]
	}, {
	    attributes: {
	        text: {
	            set: function(text, refBBox, node, attrs) {
	                if (node instanceof HTMLElement) {
	                    node.textContent = text;
	                } else {
	                    // No foreign object
	                    var style = attrs.style || {};
	                    var wrapValue = { text: text, width: -5, height: '100%' };
	                    var wrapAttrs = assign({ textVerticalAnchor: 'middle' }, style);
	                    attributes.textWrap.set.call(this, wrapValue, refBBox, node, wrapAttrs);
	                    return { fill: style.color || null };
	                }
	            },
	            position: function(text, refBBox, node) {
	                // No foreign object
	                if (node instanceof SVGElement) { return refBBox.center(); }
	            }
	        }
	    }
	});

	// LINKS

	var Link$1 = Link.define('standard.Link', {
	    attrs: {
	        line: {
	            connection: true,
	            stroke: '#333333',
	            strokeWidth: 2,
	            strokeLinejoin: 'round',
	            targetMarker: {
	                'type': 'path',
	                'd': 'M 10 -5 0 0 10 5 z'
	            }
	        },
	        wrapper: {
	            connection: true,
	            strokeWidth: 10,
	            strokeLinejoin: 'round'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'path',
	        selector: 'wrapper',
	        attributes: {
	            'fill': 'none',
	            'cursor': 'pointer',
	            'stroke': 'transparent',
	            'stroke-linecap': 'round'
	        }
	    }, {
	        tagName: 'path',
	        selector: 'line',
	        attributes: {
	            'fill': 'none',
	            'pointer-events': 'none'
	        }
	    }]
	});

	var DoubleLink = Link.define('standard.DoubleLink', {
	    attrs: {
	        line: {
	            connection: true,
	            stroke: '#DDDDDD',
	            strokeWidth: 4,
	            strokeLinejoin: 'round',
	            targetMarker: {
	                type: 'path',
	                stroke: '#000000',
	                d: 'M 10 -3 10 -10 -2 0 10 10 10 3'
	            }
	        },
	        outline: {
	            connection: true,
	            stroke: '#000000',
	            strokeWidth: 6,
	            strokeLinejoin: 'round'
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'path',
	        selector: 'outline',
	        attributes: {
	            'fill': 'none'
	        }
	    }, {
	        tagName: 'path',
	        selector: 'line',
	        attributes: {
	            'fill': 'none'
	        }
	    }]
	});

	var ShadowLink = Link.define('standard.ShadowLink', {
	    attrs: {
	        line: {
	            connection: true,
	            stroke: '#FF0000',
	            strokeWidth: 20,
	            strokeLinejoin: 'round',
	            targetMarker: {
	                'type': 'path',
	                'stroke': 'none',
	                'd': 'M 0 -10 -10 0 0 10 z'
	            },
	            sourceMarker: {
	                'type': 'path',
	                'stroke': 'none',
	                'd': 'M -10 -10 0 0 -10 10 0 10 0 -10 z'
	            }
	        },
	        shadow: {
	            connection: true,
	            refX: 3,
	            refY: 6,
	            stroke: '#000000',
	            strokeOpacity: 0.2,
	            strokeWidth: 20,
	            strokeLinejoin: 'round',
	            targetMarker: {
	                'type': 'path',
	                'd': 'M 0 -10 -10 0 0 10 z',
	                'stroke': 'none'
	            },
	            sourceMarker: {
	                'type': 'path',
	                'stroke': 'none',
	                'd': 'M -10 -10 0 0 -10 10 0 10 0 -10 z'
	            }
	        }
	    }
	}, {
	    markup: [{
	        tagName: 'path',
	        selector: 'shadow',
	        attributes: {
	            'fill': 'none'
	        }
	    }, {
	        tagName: 'path',
	        selector: 'line',
	        attributes: {
	            'fill': 'none'
	        }
	    }]
	});

	var standard = ({
		Rectangle: Rectangle,
		Circle: Circle$1,
		Ellipse: Ellipse$2,
		Path: Path$2,
		Polygon: Polygon$2,
		Polyline: Polyline$2,
		Image: Image$1,
		BorderedImage: BorderedImage,
		EmbeddedImage: EmbeddedImage,
		InscribedImage: InscribedImage,
		HeaderedRectangle: HeaderedRectangle,
		Cylinder: Cylinder,
		TextBlock: TextBlock$1,
		Link: Link$1,
		DoubleLink: DoubleLink,
		ShadowLink: ShadowLink
	});

	/**
	 * @deprecated use the port api instead
	 */
	var Model = Generic.define('devs.Model', {
	    inPorts: [],
	    outPorts: [],
	    size: {
	        width: 80,
	        height: 80
	    },
	    attrs: {
	        '.': {
	            magnet: false
	        },
	        '.label': {
	            text: 'Model',
	            'ref-x': .5,
	            'ref-y': 10,
	            'font-size': 18,
	            'text-anchor': 'middle',
	            fill: '#000'
	        },
	        '.body': {
	            'ref-width': '100%',
	            'ref-height': '100%',
	            stroke: '#000'
	        }
	    },
	    ports: {
	        groups: {
	            'in': {
	                position: {
	                    name: 'left'
	                },
	                attrs: {
	                    '.port-label': {
	                        fill: '#000'
	                    },
	                    '.port-body': {
	                        fill: '#fff',
	                        stroke: '#000',
	                        r: 10,
	                        magnet: true
	                    }
	                },
	                label: {
	                    position: {
	                        name: 'left',
	                        args: {
	                            y: 10
	                        }
	                    }
	                }
	            },
	            'out': {
	                position: {
	                    name: 'right'
	                },
	                attrs: {
	                    '.port-label': {
	                        fill: '#000'
	                    },
	                    '.port-body': {
	                        fill: '#fff',
	                        stroke: '#000',
	                        r: 10,
	                        magnet: true
	                    }
	                },
	                label: {
	                    position: {
	                        name: 'right',
	                        args: {
	                            y: 10
	                        }
	                    }
	                }
	            }
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><rect class="body"/><text class="label"/></g>',
	    portMarkup: '<circle class="port-body"/>',
	    portLabelMarkup: '<text class="port-label"/>',

	    initialize: function() {

	        Generic.prototype.initialize.apply(this, arguments);

	        this.on('change:inPorts change:outPorts', this.updatePortItems, this);
	        this.updatePortItems();
	    },

	    updatePortItems: function(model, changed, opt) {

	        // Make sure all ports are unique.
	        var inPorts = uniq(this.get('inPorts'));
	        var outPorts = difference(uniq(this.get('outPorts')), inPorts);

	        var inPortItems = this.createPortItems('in', inPorts);
	        var outPortItems = this.createPortItems('out', outPorts);

	        this.prop('ports/items', inPortItems.concat(outPortItems), assign({ rewrite: true }, opt));
	    },

	    createPortItem: function(group, port) {

	        return {
	            id: port,
	            group: group,
	            attrs: {
	                '.port-label': {
	                    text: port
	                }
	            }
	        };
	    },

	    createPortItems: function(group, ports) {

	        return toArray(ports).map(this.createPortItem.bind(this, group));
	    },

	    _addGroupPort: function(port, group, opt) {

	        var ports = this.get(group);
	        return this.set(group, Array.isArray(ports) ? ports.concat(port) : [port], opt);
	    },

	    addOutPort: function(port, opt) {

	        return this._addGroupPort(port, 'outPorts', opt);
	    },

	    addInPort: function(port, opt) {

	        return this._addGroupPort(port, 'inPorts', opt);
	    },

	    _removeGroupPort: function(port, group, opt) {

	        return this.set(group, without(this.get(group), port), opt);
	    },

	    removeOutPort: function(port, opt) {

	        return this._removeGroupPort(port, 'outPorts', opt);
	    },

	    removeInPort: function(port, opt) {

	        return this._removeGroupPort(port, 'inPorts', opt);
	    },

	    _changeGroup: function(group, properties, opt) {

	        return this.prop('ports/groups/' + group, isObject$1(properties) ? properties : {}, opt);
	    },

	    changeInGroup: function(properties, opt) {

	        return this._changeGroup('in', properties, opt);
	    },

	    changeOutGroup: function(properties, opt) {

	        return this._changeGroup('out', properties, opt);
	    }
	});

	var Atomic = Model.define('devs.Atomic', {
	    size: {
	        width: 80,
	        height: 80
	    },
	    attrs: {
	        '.label': {
	            text: 'Atomic'
	        }
	    }
	});

	var Coupled = Model.define('devs.Coupled', {
	    size: {
	        width: 200,
	        height: 300
	    },
	    attrs: {
	        '.label': {
	            text: 'Coupled'
	        }
	    }
	});

	var Link$2 = Link.define('devs.Link', {
	    attrs: {
	        '.connection': {
	            'stroke-width': 2
	        }
	    }
	});

	var devs = ({
		Model: Model,
		Atomic: Atomic,
		Coupled: Coupled,
		Link: Link$2
	});

	var Gate = Generic.define('logic.Gate', {
	    size: { width: 80, height: 40 },
	    attrs: {
	        '.': { magnet: false },
	        '.body': { width: 100, height: 50 },
	        circle: { r: 7, stroke: 'black', fill: 'transparent', 'stroke-width': 2 }
	    }
	}, {
	    operation: function() {
	        return true;
	    }
	});

	var IO = Gate.define('logic.IO', {
	    size: { width: 60, height: 30 },
	    attrs: {
	        '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 },
	        '.wire': { ref: '.body', 'ref-y': .5, stroke: 'black' },
	        text: {
	            fill: 'black',
	            ref: '.body', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
	            'text-anchor': 'middle',
	            'font-weight': 'bold',
	            'font-variant': 'small-caps',
	            'text-transform': 'capitalize',
	            'font-size': '14px'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><rect class="body"/></g><path class="wire"/><circle/><text/></g>',
	});

	var Input = IO.define('logic.Input', {
	    attrs: {
	        '.wire': { 'ref-dx': 0, d: 'M 0 0 L 23 0' },
	        circle: { ref: '.body', 'ref-dx': 30, 'ref-y': 0.5, magnet: true, 'class': 'output', port: 'out' },
	        text: { text: 'input' }
	    }
	});

	var Output = IO.define('logic.Output', {
	    attrs: {
	        '.wire': { 'ref-x': 0, d: 'M 0 0 L -23 0' },
	        circle: { ref: '.body', 'ref-x': -30, 'ref-y': 0.5, magnet: 'passive', 'class': 'input', port: 'in' },
	        text: { text: 'output' }
	    }
	});

	var Gate11 = Gate.define('logic.Gate11', {
	    attrs: {
	        '.input': { ref: '.body', 'ref-x': -2, 'ref-y': 0.5, magnet: 'passive', port: 'in' },
	        '.output': { ref: '.body', 'ref-dx': 2, 'ref-y': 0.5, magnet: true, port: 'out' }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><image class="body"/></g><circle class="input"/><circle class="output"/></g>',
	});

	var Gate21 = Gate.define('logic.Gate21', {
	    attrs: {
	        '.input1': { ref: '.body', 'ref-x': -2, 'ref-y': 0.3, magnet: 'passive', port: 'in1' },
	        '.input2': { ref: '.body', 'ref-x': -2, 'ref-y': 0.7, magnet: 'passive', port: 'in2' },
	        '.output': { ref: '.body', 'ref-dx': 2, 'ref-y': 0.5, magnet: true, port: 'out' }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><image class="body"/></g><circle class="input input1"/><circle  class="input input2"/><circle class="output"/></g>',
	});

	var Repeater = Gate11.define('logic.Repeater', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input) {
	        return input;
	    }
	});

	var Not = Gate11.define('logic.Not', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input) {
	        return !input;
	    }
	});

	var Or = Gate21.define('logic.Or', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input1, input2) {
	        return input1 || input2;
	    }
	});

	var And = Gate21.define('logic.And', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}

	}, {
	    operation: function(input1, input2) {
	        return input1 && input2;
	    }
	});

	var Nor = Gate21.define('logic.Nor', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input1, input2) {
	        return !(input1 || input2);
	    }
	});

	var Nand = Gate21.define('logic.Nand', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input1, input2) {
	        return !(input1 && input2);
	    }
	});

	var Xor = Gate21.define('logic.Xor', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input1, input2) {
	        return (!input1 || input2) && (input1 || !input2);
	    }
	});

	var Xnor = Gate21.define('logic.Xnor', {
	    attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64, ' }}
	}, {
	    operation: function(input1, input2) {
	        return (!input1 || !input2) && (input1 || input2);
	    }
	});

	var Wire = Link.define('logic.Wire', {
	    attrs: {
	        '.connection': { 'stroke-width': 2 },
	        '.marker-vertex': { r: 7 }
	    },

	    router: { name: 'orthogonal' },
	    connector: { name: 'rounded', args: { radius: 10 }}
	}, {
	    arrowheadMarkup: [
	        '<g class="marker-arrowhead-group marker-arrowhead-group-<%= end %>">',
	        '<circle class="marker-arrowhead" end="<%= end %>" r="7"/>',
	        '</g>'
	    ].join(''),

	    vertexMarkup: [
	        '<g class="marker-vertex-group" transform="translate(<%= x %>, <%= y %>)">',
	        '<circle class="marker-vertex" idx="<%= idx %>" r="10" />',
	        '<g class="marker-vertex-remove-group">',
	        '<path class="marker-vertex-remove-area" idx="<%= idx %>" d="M16,5.333c-7.732,0-14,4.701-14,10.5c0,1.982,0.741,3.833,2.016,5.414L2,25.667l5.613-1.441c2.339,1.317,5.237,2.107,8.387,2.107c7.732,0,14-4.701,14-10.5C30,10.034,23.732,5.333,16,5.333z" transform="translate(5, -33)"/>',
	        '<path class="marker-vertex-remove" idx="<%= idx %>" transform="scale(.8) translate(9.5, -37)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z">',
	        '<title>Remove vertex.</title>',
	        '</path>',
	        '</g>',
	        '</g>'
	    ].join('')
	});

	var logic = ({
		Gate: Gate,
		IO: IO,
		Input: Input,
		Output: Output,
		Gate11: Gate11,
		Gate21: Gate21,
		Repeater: Repeater,
		Not: Not,
		Or: Or,
		And: And,
		Nor: Nor,
		Nand: Nand,
		Xor: Xor,
		Xnor: Xnor,
		Wire: Wire
	});

	var KingWhite = Generic.define('chess.KingWhite', {
	    size: { width: 42, height: 38 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;"><path      d="M 22.5,11.63 L 22.5,6"      style="fill:none; stroke:#000000; stroke-linejoin:miter;" />    <path      d="M 20,8 L 25,8"      style="fill:none; stroke:#000000; stroke-linejoin:miter;" />    <path      d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"      style="fill:#ffffff; stroke:#000000; stroke-linecap:butt; stroke-linejoin:miter;" />    <path      d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "      style="fill:#ffffff; stroke:#000000;" />    <path      d="M 11.5,30 C 17,27 27,27 32.5,30"      style="fill:none; stroke:#000000;" />    <path      d="M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5"      style="fill:none; stroke:#000000;" />    <path      d="M 11.5,37 C 17,34 27,34 32.5,37"      style="fill:none; stroke:#000000;" />  </g></g></g>'
	});

	var KingBlack = Generic.define('chess.KingBlack', {
	    size: { width: 42, height: 38 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <path       d="M 22.5,11.63 L 22.5,6"       style="fill:none; stroke:#000000; stroke-linejoin:miter;"       id="path6570" />    <path       d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"       style="fill:#000000;fill-opacity:1; stroke-linecap:butt; stroke-linejoin:miter;" />    <path       d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "       style="fill:#000000; stroke:#000000;" />    <path       d="M 20,8 L 25,8"       style="fill:none; stroke:#000000; stroke-linejoin:miter;" />    <path       d="M 32,29.5 C 32,29.5 40.5,25.5 38.03,19.85 C 34.15,14 25,18 22.5,24.5 L 22.51,26.6 L 22.5,24.5 C 20,18 9.906,14 6.997,19.85 C 4.5,25.5 11.85,28.85 11.85,28.85"       style="fill:none; stroke:#ffffff;" />    <path       d="M 11.5,30 C 17,27 27,27 32.5,30 M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5 M 11.5,37 C 17,34 27,34 32.5,37"       style="fill:none; stroke:#ffffff;" />  </g></g></g>'
	});

	var QueenWhite = Generic.define('chess.QueenWhite', {
	    size: { width: 42, height: 38 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <path      d="M 9 13 A 2 2 0 1 1  5,13 A 2 2 0 1 1  9 13 z"      transform="translate(-1,-1)" />    <path      d="M 9 13 A 2 2 0 1 1  5,13 A 2 2 0 1 1  9 13 z"      transform="translate(15.5,-5.5)" />    <path      d="M 9 13 A 2 2 0 1 1  5,13 A 2 2 0 1 1  9 13 z"      transform="translate(32,-1)" />    <path      d="M 9 13 A 2 2 0 1 1  5,13 A 2 2 0 1 1  9 13 z"      transform="translate(7,-4.5)" />    <path      d="M 9 13 A 2 2 0 1 1  5,13 A 2 2 0 1 1  9 13 z"      transform="translate(24,-4)" />    <path      d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38,14 L 31,25 L 31,11 L 25.5,24.5 L 22.5,9.5 L 19.5,24.5 L 14,10.5 L 14,25 L 7,14 L 9,26 z "      style="stroke-linecap:butt;" />    <path      d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 10.5,36 10.5,36 C 9,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z "      style="stroke-linecap:butt;" />    <path      d="M 11.5,30 C 15,29 30,29 33.5,30"      style="fill:none;" />    <path      d="M 12,33.5 C 18,32.5 27,32.5 33,33.5"      style="fill:none;" />  </g></g></g>'
	});

	var QueenBlack = Generic.define('chess.QueenBlack', {
	    size: { width: 42, height: 38 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:#000000; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <g style="fill:#000000; stroke:none;">      <circle cx="6"    cy="12" r="2.75" />      <circle cx="14"   cy="9"  r="2.75" />      <circle cx="22.5" cy="8"  r="2.75" />      <circle cx="31"   cy="9"  r="2.75" />      <circle cx="39"   cy="12" r="2.75" />    </g>    <path       d="M 9,26 C 17.5,24.5 30,24.5 36,26 L 38.5,13.5 L 31,25 L 30.7,10.9 L 25.5,24.5 L 22.5,10 L 19.5,24.5 L 14.3,10.9 L 14,25 L 6.5,13.5 L 9,26 z"       style="stroke-linecap:butt; stroke:#000000;" />    <path       d="M 9,26 C 9,28 10.5,28 11.5,30 C 12.5,31.5 12.5,31 12,33.5 C 10.5,34.5 10.5,36 10.5,36 C 9,37.5 11,38.5 11,38.5 C 17.5,39.5 27.5,39.5 34,38.5 C 34,38.5 35.5,37.5 34,36 C 34,36 34.5,34.5 33,33.5 C 32.5,31 32.5,31.5 33.5,30 C 34.5,28 36,28 36,26 C 27.5,24.5 17.5,24.5 9,26 z"       style="stroke-linecap:butt;" />    <path       d="M 11,38.5 A 35,35 1 0 0 34,38.5"       style="fill:none; stroke:#000000; stroke-linecap:butt;" />    <path       d="M 11,29 A 35,35 1 0 1 34,29"       style="fill:none; stroke:#ffffff;" />    <path       d="M 12.5,31.5 L 32.5,31.5"       style="fill:none; stroke:#ffffff;" />    <path       d="M 11.5,34.5 A 35,35 1 0 0 33.5,34.5"       style="fill:none; stroke:#ffffff;" />    <path       d="M 10.5,37.5 A 35,35 1 0 0 34.5,37.5"       style="fill:none; stroke:#ffffff;" />  </g></g></g>'
	});

	var RookWhite = Generic.define('chess.RookWhite', {
	    size: { width: 32, height: 34 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <path      d="M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z "      style="stroke-linecap:butt;" />    <path      d="M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z "      style="stroke-linecap:butt;" />    <path      d="M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14"      style="stroke-linecap:butt;" />    <path      d="M 34,14 L 31,17 L 14,17 L 11,14" />    <path      d="M 31,17 L 31,29.5 L 14,29.5 L 14,17"      style="stroke-linecap:butt; stroke-linejoin:miter;" />    <path      d="M 31,29.5 L 32.5,32 L 12.5,32 L 14,29.5" />    <path      d="M 11,14 L 34,14"      style="fill:none; stroke:#000000; stroke-linejoin:miter;" />  </g></g></g>'
	});

	var RookBlack = Generic.define('chess.RookBlack', {
	    size: { width: 32, height: 34 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:#000000; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <path      d="M 9,39 L 36,39 L 36,36 L 9,36 L 9,39 z "      style="stroke-linecap:butt;" />    <path      d="M 12.5,32 L 14,29.5 L 31,29.5 L 32.5,32 L 12.5,32 z "      style="stroke-linecap:butt;" />    <path      d="M 12,36 L 12,32 L 33,32 L 33,36 L 12,36 z "      style="stroke-linecap:butt;" />    <path      d="M 14,29.5 L 14,16.5 L 31,16.5 L 31,29.5 L 14,29.5 z "      style="stroke-linecap:butt;stroke-linejoin:miter;" />    <path      d="M 14,16.5 L 11,14 L 34,14 L 31,16.5 L 14,16.5 z "      style="stroke-linecap:butt;" />    <path      d="M 11,14 L 11,9 L 15,9 L 15,11 L 20,11 L 20,9 L 25,9 L 25,11 L 30,11 L 30,9 L 34,9 L 34,14 L 11,14 z "      style="stroke-linecap:butt;" />    <path      d="M 12,35.5 L 33,35.5 L 33,35.5"      style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />    <path      d="M 13,31.5 L 32,31.5"      style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />    <path      d="M 14,29.5 L 31,29.5"      style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />    <path      d="M 14,16.5 L 31,16.5"      style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />    <path      d="M 11,14 L 34,14"      style="fill:none; stroke:#ffffff; stroke-width:1; stroke-linejoin:miter;" />  </g></g></g>'
	});

	var BishopWhite = Generic.define('chess.BishopWhite', {
	    size: { width: 38, height: 38 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <g style="fill:#ffffff; stroke:#000000; stroke-linecap:butt;">       <path        d="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.646,38.99 6.677,38.97 6,38 C 7.354,36.06 9,36 9,36 z" />      <path        d="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z" />      <path        d="M 25 8 A 2.5 2.5 0 1 1  20,8 A 2.5 2.5 0 1 1  25 8 z" />    </g>    <path      d="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18"      style="fill:none; stroke:#000000; stroke-linejoin:miter;" />  </g></g></g>'
	});

	var BishopBlack = Generic.define('chess.BishopBlack', {
	    size: { width: 38, height: 38 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:none; fill-rule:evenodd; fill-opacity:1; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <g style="fill:#000000; stroke:#000000; stroke-linecap:butt;">       <path        d="M 9,36 C 12.39,35.03 19.11,36.43 22.5,34 C 25.89,36.43 32.61,35.03 36,36 C 36,36 37.65,36.54 39,38 C 38.32,38.97 37.35,38.99 36,38.5 C 32.61,37.53 25.89,38.96 22.5,37.5 C 19.11,38.96 12.39,37.53 9,38.5 C 7.646,38.99 6.677,38.97 6,38 C 7.354,36.06 9,36 9,36 z" />      <path        d="M 15,32 C 17.5,34.5 27.5,34.5 30,32 C 30.5,30.5 30,30 30,30 C 30,27.5 27.5,26 27.5,26 C 33,24.5 33.5,14.5 22.5,10.5 C 11.5,14.5 12,24.5 17.5,26 C 17.5,26 15,27.5 15,30 C 15,30 14.5,30.5 15,32 z" />      <path        d="M 25 8 A 2.5 2.5 0 1 1  20,8 A 2.5 2.5 0 1 1  25 8 z" />    </g>    <path       d="M 17.5,26 L 27.5,26 M 15,30 L 30,30 M 22.5,15.5 L 22.5,20.5 M 20,18 L 25,18"       style="fill:none; stroke:#ffffff; stroke-linejoin:miter;" />  </g></g></g>'
	});

	var KnightWhite = Generic.define('chess.KnightWhite', {
	    size: { width: 38, height: 37 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <path      d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"      style="fill:#ffffff; stroke:#000000;" />    <path      d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"      style="fill:#ffffff; stroke:#000000;" />    <path      d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"      style="fill:#000000; stroke:#000000;" />    <path      d="M 15 15.5 A 0.5 1.5 0 1 1  14,15.5 A 0.5 1.5 0 1 1  15 15.5 z"      transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)"      style="fill:#000000; stroke:#000000;" />  </g></g></g>'
	});

	var KnightBlack = Generic.define('chess.KnightBlack', {
	    size: { width: 38, height: 37 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><g style="opacity:1; fill:none; fill-opacity:1; fill-rule:evenodd; stroke:#000000; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;">    <path      d="M 22,10 C 32.5,11 38.5,18 38,39 L 15,39 C 15,30 25,32.5 23,18"      style="fill:#000000; stroke:#000000;" />    <path      d="M 24,18 C 24.38,20.91 18.45,25.37 16,27 C 13,29 13.18,31.34 11,31 C 9.958,30.06 12.41,27.96 11,28 C 10,28 11.19,29.23 10,30 C 9,30 5.997,31 6,26 C 6,24 12,14 12,14 C 12,14 13.89,12.1 14,10.5 C 13.27,9.506 13.5,8.5 13.5,7.5 C 14.5,6.5 16.5,10 16.5,10 L 18.5,10 C 18.5,10 19.28,8.008 21,7 C 22,7 22,10 22,10"      style="fill:#000000; stroke:#000000;" />    <path      d="M 9.5 25.5 A 0.5 0.5 0 1 1 8.5,25.5 A 0.5 0.5 0 1 1 9.5 25.5 z"      style="fill:#ffffff; stroke:#ffffff;" />    <path      d="M 15 15.5 A 0.5 1.5 0 1 1  14,15.5 A 0.5 1.5 0 1 1  15 15.5 z"      transform="matrix(0.866,0.5,-0.5,0.866,9.693,-5.173)"      style="fill:#ffffff; stroke:#ffffff;" />    <path      d="M 24.55,10.4 L 24.1,11.85 L 24.6,12 C 27.75,13 30.25,14.49 32.5,18.75 C 34.75,23.01 35.75,29.06 35.25,39 L 35.2,39.5 L 37.45,39.5 L 37.5,39 C 38,28.94 36.62,22.15 34.25,17.66 C 31.88,13.17 28.46,11.02 25.06,10.5 L 24.55,10.4 z "      style="fill:#ffffff; stroke:none;" />  </g></g></g>'
	});

	var PawnWhite = Generic.define('chess.PawnWhite', {
	    size: { width: 28, height: 33 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><path d="M 22,9 C 19.79,9 18,10.79 18,13 C 18,13.89 18.29,14.71 18.78,15.38 C 16.83,16.5 15.5,18.59 15.5,21 C 15.5,23.03 16.44,24.84 17.91,26.03 C 14.91,27.09 10.5,31.58 10.5,39.5 L 33.5,39.5 C 33.5,31.58 29.09,27.09 26.09,26.03 C 27.56,24.84 28.5,23.03 28.5,21 C 28.5,18.59 27.17,16.5 25.22,15.38 C 25.71,14.71 26,13.89 26,13 C 26,10.79 24.21,9 22,9 z "  style="opacity:1; fill:#ffffff; fill-opacity:1; fill-rule:nonzero; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" /></g></g>'
	});

	var PawnBlack = Generic.define('chess.PawnBlack', {
	    size: { width: 28, height: 33 }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><path d="M 22,9 C 19.79,9 18,10.79 18,13 C 18,13.89 18.29,14.71 18.78,15.38 C 16.83,16.5 15.5,18.59 15.5,21 C 15.5,23.03 16.44,24.84 17.91,26.03 C 14.91,27.09 10.5,31.58 10.5,39.5 L 33.5,39.5 C 33.5,31.58 29.09,27.09 26.09,26.03 C 27.56,24.84 28.5,23.03 28.5,21 C 28.5,18.59 27.17,16.5 25.22,15.38 C 25.71,14.71 26,13.89 26,13 C 26,10.79 24.21,9 22,9 z "  style="opacity:1; fill:#000000; fill-opacity:1; fill-rule:nonzero; stroke:#000000; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:miter; stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" /></g></g>'
	});

	var chess = ({
		KingWhite: KingWhite,
		KingBlack: KingBlack,
		QueenWhite: QueenWhite,
		QueenBlack: QueenBlack,
		RookWhite: RookWhite,
		RookBlack: RookBlack,
		BishopWhite: BishopWhite,
		BishopBlack: BishopBlack,
		KnightWhite: KnightWhite,
		KnightBlack: KnightBlack,
		PawnWhite: PawnWhite,
		PawnBlack: PawnBlack
	});

	var Entity = Element$1.define('erd.Entity', {
	    size: { width: 150, height: 60 },
	    attrs: {
	        '.outer': {
	            fill: '#2ECC71', stroke: '#27AE60', 'stroke-width': 2,
	            points: '100,0 100,60 0,60 0,0'
	        },
	        '.inner': {
	            fill: '#2ECC71', stroke: '#27AE60', 'stroke-width': 2,
	            points: '95,5 95,55 5,55 5,5',
	            display: 'none'
	        },
	        text: {
	            text: 'Entity',
	            'font-family': 'Arial', 'font-size': 14,
	            'ref-x': .5, 'ref-y': .5,
	            'y-alignment': 'middle', 'text-anchor': 'middle'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><polygon class="outer"/><polygon class="inner"/></g><text/></g>',
	});

	var WeakEntity = Entity.define('erd.WeakEntity', {
	    attrs: {
	        '.inner': { display: 'auto' },
	        text: { text: 'Weak Entity' }
	    }
	});

	var Relationship = Element$1.define('erd.Relationship', {
	    size: { width: 80, height: 80 },
	    attrs: {
	        '.outer': {
	            fill: '#3498DB', stroke: '#2980B9', 'stroke-width': 2,
	            points: '40,0 80,40 40,80 0,40'
	        },
	        '.inner': {
	            fill: '#3498DB', stroke: '#2980B9', 'stroke-width': 2,
	            points: '40,5 75,40 40,75 5,40',
	            display: 'none'
	        },
	        text: {
	            text: 'Relationship',
	            'font-family': 'Arial', 'font-size': 12,
	            'ref-x': .5, 'ref-y': .5,
	            'y-alignment': 'middle', 'text-anchor': 'middle'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><polygon class="outer"/><polygon class="inner"/></g><text/></g>',
	});

	var IdentifyingRelationship = Relationship.define('erd.IdentifyingRelationship', {
	    attrs: {
	        '.inner': { display: 'auto' },
	        text: { text: 'Identifying' }
	    }
	});

	var Attribute = Element$1.define('erd.Attribute', {
	    size: { width: 100, height: 50 },
	    attrs: {
	        'ellipse': {
	            transform: 'translate(50, 25)'
	        },
	        '.outer': {
	            stroke: '#D35400', 'stroke-width': 2,
	            cx: 0, cy: 0, rx: 50, ry: 25,
	            fill: '#E67E22'
	        },
	        '.inner': {
	            stroke: '#D35400', 'stroke-width': 2,
	            cx: 0, cy: 0, rx: 45, ry: 20,
	            fill: '#E67E22', display: 'none'
	        },
	        text: {
	            'font-family': 'Arial', 'font-size': 14,
	            'ref-x': .5, 'ref-y': .5,
	            'y-alignment': 'middle', 'text-anchor': 'middle'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><ellipse class="outer"/><ellipse class="inner"/></g><text/></g>',
	});

	var Multivalued = Attribute.define('erd.Multivalued', {
	    attrs: {
	        '.inner': { display: 'block' },
	        text: { text: 'multivalued' }
	    }
	});

	var Derived = Attribute.define('erd.Derived', {
	    attrs: {
	        '.outer': { 'stroke-dasharray': '3,5' },
	        text: { text: 'derived' }
	    }
	});

	var Key = Attribute.define('erd.Key', {
	    attrs: {
	        ellipse: { 'stroke-width': 4 },
	        text: { text: 'key', 'font-weight': '800', 'text-decoration': 'underline' }
	    }
	});

	var Normal = Attribute.define('erd.Normal', {
	    attrs: { text: { text: 'Normal' }}
	});

	var ISA = Element$1.define('erd.ISA', {
	    type: 'erd.ISA',
	    size: { width: 100, height: 50 },
	    attrs: {
	        polygon: {
	            points: '0,0 50,50 100,0',
	            fill: '#F1C40F', stroke: '#F39C12', 'stroke-width': 2
	        },
	        text: {
	            text: 'ISA', 'font-size': 18,
	            'ref-x': .5, 'ref-y': .3,
	            'y-alignment': 'middle', 'text-anchor': 'middle'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><polygon/></g><text/></g>',
	});

	var Line$1 = Link.define('erd.Line', {}, {
	    cardinality: function(value) {
	        this.set('labels', [{ position: -20, attrs: { text: { dy: -8, text: value }}}]);
	    }
	});

	var erd = ({
		Entity: Entity,
		WeakEntity: WeakEntity,
		Relationship: Relationship,
		IdentifyingRelationship: IdentifyingRelationship,
		Attribute: Attribute,
		Multivalued: Multivalued,
		Derived: Derived,
		Key: Key,
		Normal: Normal,
		ISA: ISA,
		Line: Line$1
	});

	var State = Circle.define('fsa.State', {
	    attrs: {
	        circle: { 'stroke-width': 3 },
	        text: { 'font-weight': '800' }
	    }
	});

	var StartState = Element$1.define('fsa.StartState', {
	    size: { width: 20, height: 20 },
	    attrs: {
	        circle: {
	            transform: 'translate(10, 10)',
	            r: 10,
	            fill: '#000000'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><circle/></g></g>',
	});

	var EndState = Element$1.define('fsa.EndState', {
	    size: { width: 20, height: 20 },
	    attrs: {
	        '.outer': {
	            transform: 'translate(10, 10)',
	            r: 10,
	            fill: '#ffffff',
	            stroke: '#000000'
	        },

	        '.inner': {
	            transform: 'translate(10, 10)',
	            r: 6,
	            fill: '#000000'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><circle class="outer"/><circle class="inner"/></g></g>',
	});

	var Arrow = Link.define('fsa.Arrow', {
	    attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' }},
	    smooth: true
	});

	var fsa = ({
		State: State,
		StartState: StartState,
		EndState: EndState,
		Arrow: Arrow
	});

	var Member = Element$1.define('org.Member', {
	    size: { width: 180, height: 70 },
	    attrs: {
	        rect: { width: 170, height: 60 },

	        '.card': {
	            fill: '#FFFFFF', stroke: '#000000', 'stroke-width': 2,
	            'pointer-events': 'visiblePainted', rx: 10, ry: 10
	        },

	        image: {
	            width: 48, height: 48,
	            ref: '.card', 'ref-x': 10, 'ref-y': 5
	        },

	        '.rank': {
	            'text-decoration': 'underline',
	            ref: '.card', 'ref-x': 0.9, 'ref-y': 0.2,
	            'font-family': 'Courier New', 'font-size': 14,
	            'text-anchor': 'end'
	        },

	        '.name': {
	            'font-weight': '800',
	            ref: '.card', 'ref-x': 0.9, 'ref-y': 0.6,
	            'font-family': 'Courier New', 'font-size': 14,
	            'text-anchor': 'end'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><rect class="card"/><image/></g><text class="rank"/><text class="name"/></g>',
	});

	var Arrow$1 = Link.define('org.Arrow', {
	    source: { selector: '.card' }, target: { selector: '.card' },
	    attrs: { '.connection': { stroke: '#585858', 'stroke-width': 3 }},
	    z: -1
	});

	var org = ({
		Member: Member,
		Arrow: Arrow$1
	});

	var Place = Generic.define('pn.Place', {
	    size: { width: 50, height: 50 },
	    attrs: {
	        '.root': {
	            r: 25,
	            fill: '#ffffff',
	            stroke: '#000000',
	            transform: 'translate(25, 25)'
	        },
	        '.label': {
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-y': -20,
	            ref: '.root',
	            fill: '#000000',
	            'font-size': 12
	        },
	        '.tokens > circle': {
	            fill: '#000000',
	            r: 5
	        },
	        '.tokens.one > circle': { transform: 'translate(25, 25)' },

	        '.tokens.two > circle:nth-child(1)': { transform: 'translate(19, 25)' },
	        '.tokens.two > circle:nth-child(2)': { transform: 'translate(31, 25)' },

	        '.tokens.three > circle:nth-child(1)': { transform: 'translate(18, 29)' },
	        '.tokens.three > circle:nth-child(2)': { transform: 'translate(25, 19)' },
	        '.tokens.three > circle:nth-child(3)': { transform: 'translate(32, 29)' },

	        '.tokens.alot > text': {
	            transform: 'translate(25, 18)',
	            'text-anchor': 'middle',
	            fill: '#000000'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><circle class="root"/><g class="tokens" /></g><text class="label"/></g>',
	});

	var PlaceView = ElementView.extend({

	    presentationAttributes: ElementView.addPresentationAttributes({
	        tokens: ['TOKENS']
	    }),

	    initFlag: ElementView.prototype.initFlag.concat(['TOKENS']),

	    confirmUpdate: function() {
	        var ref;

	        var args = [], len = arguments.length;
	        while ( len-- ) args[ len ] = arguments[ len ];
	        var flags = (ref = ElementView.prototype.confirmUpdate).call.apply(ref, [ this ].concat( args ));
	        if (this.hasFlag(flags, 'TOKENS')) {
	            this.renderTokens();
	            this.update();
	            flags = this.removeFlag(flags, 'TOKENS');
	        }
	        return flags;
	    },

	    renderTokens: function() {

	        var vTokens = this.vel.findOne('.tokens').empty();
	        ['one', 'two', 'three', 'alot'].forEach(function(className) {
	            vTokens.removeClass(className);
	        });

	        var tokens = this.model.get('tokens');
	        if (!tokens) { return; }

	        switch (tokens) {

	            case 1:
	                vTokens.addClass('one');
	                vTokens.append(V('circle'));
	                break;

	            case 2:
	                vTokens.addClass('two');
	                vTokens.append([V('circle'), V('circle')]);
	                break;

	            case 3:
	                vTokens.addClass('three');
	                vTokens.append([V('circle'), V('circle'), V('circle')]);
	                break;

	            default:
	                vTokens.addClass('alot');
	                vTokens.append(V('text').text(tokens + ''));
	                break;
	        }
	    }
	});

	var Transition = Generic.define('pn.Transition', {
	    size: { width: 12, height: 50 },
	    attrs: {
	        'rect': {
	            width: 12,
	            height: 50,
	            fill: '#000000',
	            stroke: '#000000'
	        },
	        '.label': {
	            'text-anchor': 'middle',
	            'ref-x': .5,
	            'ref-y': -20,
	            ref: 'rect',
	            fill: '#000000',
	            'font-size': 12
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><rect class="root"/></g></g><text class="label"/>',
	});

	var Link$3 = Link.define('pn.Link', {
	    attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' }}
	});

	var pn = ({
		Place: Place,
		PlaceView: PlaceView,
		Transition: Transition,
		Link: Link$3
	});

	var Class = Generic.define('uml.Class', {
	    attrs: {
	        rect: { 'width': 200 },

	        '.uml-class-name-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#3498db' },
	        '.uml-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#2980b9' },
	        '.uml-class-methods-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#2980b9' },

	        '.uml-class-name-text': {
	            'ref': '.uml-class-name-rect',
	            'ref-y': .5,
	            'ref-x': .5,
	            'text-anchor': 'middle',
	            'y-alignment': 'middle',
	            'font-weight': 'bold',
	            'fill': 'black',
	            'font-size': 12,
	            'font-family': 'Times New Roman'
	        },
	        '.uml-class-attrs-text': {
	            'ref': '.uml-class-attrs-rect', 'ref-y': 5, 'ref-x': 5,
	            'fill': 'black', 'font-size': 12, 'font-family': 'Times New Roman'
	        },
	        '.uml-class-methods-text': {
	            'ref': '.uml-class-methods-rect', 'ref-y': 5, 'ref-x': 5,
	            'fill': 'black', 'font-size': 12, 'font-family': 'Times New Roman'
	        }
	    },

	    name: [],
	    attributes: [],
	    methods: []
	}, {
	    markup: [
	        '<g class="rotatable">',
	        '<g class="scalable">',
	        '<rect class="uml-class-name-rect"/><rect class="uml-class-attrs-rect"/><rect class="uml-class-methods-rect"/>',
	        '</g>',
	        '<text class="uml-class-name-text"/><text class="uml-class-attrs-text"/><text class="uml-class-methods-text"/>',
	        '</g>'
	    ].join(''),

	    initialize: function() {

	        this.on('change:name change:attributes change:methods', function() {
	            this.updateRectangles();
	            this.trigger('uml-update');
	        }, this);

	        this.updateRectangles();

	        Generic.prototype.initialize.apply(this, arguments);
	    },

	    getClassName: function() {
	        return this.get('name');
	    },

	    updateRectangles: function() {

	        var attrs = this.get('attrs');

	        var rects = [
	            { type: 'name', text: this.getClassName() },
	            { type: 'attrs', text: this.get('attributes') },
	            { type: 'methods', text: this.get('methods') }
	        ];

	        var offsetY = 0;

	        rects.forEach(function(rect) {

	            var lines = Array.isArray(rect.text) ? rect.text : [rect.text];
	            var rectHeight = lines.length * 20 + 20;

	            attrs['.uml-class-' + rect.type + '-text'].text = lines.join('\n');
	            attrs['.uml-class-' + rect.type + '-rect'].height = rectHeight;
	            attrs['.uml-class-' + rect.type + '-rect'].transform = 'translate(0,' + offsetY + ')';

	            offsetY += rectHeight;
	        });
	    }

	});

	var ClassView = ElementView.extend({

	    initialize: function() {

	        ElementView.prototype.initialize.apply(this, arguments);

	        this.listenTo(this.model, 'uml-update', function() {
	            this.update();
	            this.resize();
	        });
	    }
	});

	var Abstract = Class.define('uml.Abstract', {
	    attrs: {
	        '.uml-class-name-rect': { fill: '#e74c3c' },
	        '.uml-class-attrs-rect': { fill: '#c0392b' },
	        '.uml-class-methods-rect': { fill: '#c0392b' }
	    }
	}, {

	    getClassName: function() {
	        return ['<<Abstract>>', this.get('name')];
	    }

	});
	var AbstractView = ClassView;

	var Interface = Class.define('uml.Interface', {
	    attrs: {
	        '.uml-class-name-rect': { fill: '#f1c40f' },
	        '.uml-class-attrs-rect': { fill: '#f39c12' },
	        '.uml-class-methods-rect': { fill: '#f39c12' }
	    }
	}, {
	    getClassName: function() {
	        return ['<<Interface>>', this.get('name')];
	    }
	});
	var InterfaceView = ClassView;

	var Generalization = Link.define('uml.Generalization', {
	    attrs: { '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' }}
	});

	var Implementation = Link.define('uml.Implementation', {
	    attrs: {
	        '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' },
	        '.connection': { 'stroke-dasharray': '3,3' }
	    }
	});

	var Aggregation = Link.define('uml.Aggregation', {
	    attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'white' }}
	});

	var Composition = Link.define('uml.Composition', {
	    attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'black' }}
	});

	var Association = Link.define('uml.Association');

	// Statechart

	var State$1 = Generic.define('uml.State', {
	    attrs: {
	        '.uml-state-body': {
	            'width': 200, 'height': 200, 'rx': 10, 'ry': 10,
	            'fill': '#ecf0f1', 'stroke': '#bdc3c7', 'stroke-width': 3
	        },
	        '.uml-state-separator': {
	            'stroke': '#bdc3c7', 'stroke-width': 2
	        },
	        '.uml-state-name': {
	            'ref': '.uml-state-body', 'ref-x': .5, 'ref-y': 5, 'text-anchor': 'middle',
	            'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14
	        },
	        '.uml-state-events': {
	            'ref': '.uml-state-separator', 'ref-x': 5, 'ref-y': 5,
	            'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14
	        }
	    },

	    name: 'State',
	    events: []

	}, {
	    markup: [
	        '<g class="rotatable">',
	        '<g class="scalable">',
	        '<rect class="uml-state-body"/>',
	        '</g>',
	        '<path class="uml-state-separator"/>',
	        '<text class="uml-state-name"/>',
	        '<text class="uml-state-events"/>',
	        '</g>'
	    ].join(''),

	    initialize: function() {

	        this.on({
	            'change:name': this.updateName,
	            'change:events': this.updateEvents,
	            'change:size': this.updatePath
	        }, this);

	        this.updateName();
	        this.updateEvents();
	        this.updatePath();

	        Generic.prototype.initialize.apply(this, arguments);
	    },

	    updateName: function() {

	        this.attr('.uml-state-name/text', this.get('name'));
	    },

	    updateEvents: function() {

	        this.attr('.uml-state-events/text', this.get('events').join('\n'));
	    },

	    updatePath: function() {

	        var d = 'M 0 20 L ' + this.get('size').width + ' 20';

	        // We are using `silent: true` here because updatePath() is meant to be called
	        // on resize and there's no need to to update the element twice (`change:size`
	        // triggers also an update).
	        this.attr('.uml-state-separator/d', d, { silent: true });
	    }
	});

	var StartState$1 = Circle.define('uml.StartState', {
	    type: 'uml.StartState',
	    attrs: { circle: { 'fill': '#34495e', 'stroke': '#2c3e50', 'stroke-width': 2, 'rx': 1 }}
	});

	var EndState$1 = Generic.define('uml.EndState', {
	    size: { width: 20, height: 20 },
	    attrs: {
	        'circle.outer': {
	            transform: 'translate(10, 10)',
	            r: 10,
	            fill: '#ffffff',
	            stroke: '#2c3e50'
	        },

	        'circle.inner': {
	            transform: 'translate(10, 10)',
	            r: 6,
	            fill: '#34495e'
	        }
	    }
	}, {
	    markup: '<g class="rotatable"><g class="scalable"><circle class="outer"/><circle class="inner"/></g></g>',
	});

	var Transition$1 = Link.define('uml.Transition', {
	    attrs: {
	        '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', fill: '#34495e', stroke: '#2c3e50' },
	        '.connection': { stroke: '#2c3e50' }
	    }
	});

	var uml = ({
		Class: Class,
		ClassView: ClassView,
		Abstract: Abstract,
		AbstractView: AbstractView,
		Interface: Interface,
		InterfaceView: InterfaceView,
		Generalization: Generalization,
		Implementation: Implementation,
		Aggregation: Aggregation,
		Composition: Composition,
		Association: Association,
		State: State$1,
		StartState: StartState$1,
		EndState: EndState$1,
		Transition: Transition$1
	});



	var index$3 = ({
		basic: basic,
		standard: standard,
		devs: devs,
		logic: logic,
		chess: chess,
		erd: erd,
		fsa: fsa,
		org: org,
		pn: pn,
		uml: uml
	});

	function abs2rel(absolute, max) {

	    if (max === 0) { return '0%'; }
	    // round to 3 decimal places
	    var dp = 1000;
	    var relative = Math.round(absolute / max * 100 * dp) / dp;
	    return (relative + "%");
	}

	function pin(relative) {

	    return function(end, view, magnet, coords) {
	        var fn = (view.isNodeConnection(magnet)) ? pinnedLinkEnd : pinnedElementEnd;
	        return fn(relative, end, view, magnet, coords);
	    };
	}

	function pinnedElementEnd(relative, end, view, magnet, coords) {

	    var angle = view.model.angle();
	    var bbox = view.getNodeUnrotatedBBox(magnet);
	    var origin = view.model.getBBox().center();
	    coords.rotate(origin, angle);
	    var dx = coords.x - bbox.x;
	    var dy = coords.y - bbox.y;

	    if (relative) {
	        dx = abs2rel(dx, bbox.width);
	        dy = abs2rel(dy, bbox.height);
	    }

	    end.anchor = {
	        name: 'topLeft',
	        args: {
	            dx: dx,
	            dy: dy,
	            rotate: true
	        }
	    };

	    return end;
	}

	function pinnedLinkEnd(relative, end, view, _magnet, coords) {

	    var connection = view.getConnection();
	    if (!connection) { return end; }
	    var length = connection.closestPointLength(coords);
	    if (relative) {
	        var totalLength = connection.length();
	        end.anchor = {
	            name: 'connectionRatio',
	            args: {
	                ratio: length / totalLength
	            }
	        };
	    } else {
	        end.anchor = {
	            name: 'connectionLength',
	            args: {
	                length: length
	            }
	        };
	    }
	    return end;
	}

	var useDefaults = noop;
	var pinAbsolute = pin(false);
	var pinRelative = pin(true);

	var index$4 = ({
		useDefaults: useDefaults,
		pinAbsolute: pinAbsolute,
		pinRelative: pinRelative
	});

	// Vertex Handles
	var VertexHandle = View.extend({
	    tagName: 'circle',
	    svgElement: true,
	    className: 'marker-vertex',
	    events: {
	        mousedown: 'onPointerDown',
	        touchstart: 'onPointerDown',
	        dblclick: 'onDoubleClick',
	        dbltap: 'onDoubleClick'
	    },
	    documentEvents: {
	        mousemove: 'onPointerMove',
	        touchmove: 'onPointerMove',
	        mouseup: 'onPointerUp',
	        touchend: 'onPointerUp',
	        touchcancel: 'onPointerUp'
	    },
	    attributes: {
	        'r': 6,
	        'fill': '#33334F',
	        'stroke': '#FFFFFF',
	        'stroke-width': 2,
	        'cursor': 'move'
	    },
	    position: function(x, y) {
	        var ref = this;
	        var vel = ref.vel;
	        var options = ref.options;
	        var scale = options.scale;
	        var matrix = V.createSVGMatrix().translate(x, y);
	        if (scale) { matrix = matrix.scale(scale); }
	        vel.transform(matrix, { absolute: true });
	    },
	    onPointerDown: function(evt) {
	        if (this.options.guard(evt)) { return; }
	        evt.stopPropagation();
	        evt.preventDefault();
	        this.options.paper.undelegateEvents();
	        this.delegateDocumentEvents(null, evt.data);
	        this.trigger('will-change', this, evt);
	    },
	    onPointerMove: function(evt) {
	        this.trigger('changing', this, evt);
	    },
	    onDoubleClick: function(evt) {
	        this.trigger('remove', this, evt);
	    },
	    onPointerUp: function(evt) {
	        this.trigger('changed', this, evt);
	        this.undelegateDocumentEvents();
	        this.options.paper.delegateEvents();
	    }
	});

	var Vertices = ToolView.extend({
	    name: 'vertices',
	    options: {
	        handleClass: VertexHandle,
	        snapRadius: 20,
	        redundancyRemoval: true,
	        vertexAdding: true,
	        stopPropagation: true,
	        scale: null
	    },
	    children: [{
	        tagName: 'path',
	        selector: 'connection',
	        className: 'joint-vertices-path',
	        attributes: {
	            'fill': 'none',
	            'stroke': 'transparent',
	            'stroke-width': 10,
	            'cursor': 'cell'
	        }
	    }],
	    handles: null,
	    events: {
	        'mousedown .joint-vertices-path': 'onPathPointerDown',
	        'touchstart .joint-vertices-path': 'onPathPointerDown'
	    },
	    onRender: function() {
	        if (this.options.vertexAdding) {
	            this.renderChildren();
	            this.updatePath();
	        }
	        this.resetHandles();
	        this.renderHandles();
	        return this;
	    },
	    update: function() {
	        var relatedView = this.relatedView;
	        var vertices = relatedView.model.vertices();
	        if (vertices.length === this.handles.length) {
	            this.updateHandles();
	        } else {
	            this.resetHandles();
	            this.renderHandles();
	        }
	        if (this.options.vertexAdding) {
	            this.updatePath();
	        }
	        return this;
	    },
	    resetHandles: function() {
	        var handles = this.handles;
	        this.handles = [];
	        this.stopListening();
	        if (!Array.isArray(handles)) { return; }
	        for (var i = 0, n = handles.length; i < n; i++) {
	            handles[i].remove();
	        }
	    },
	    renderHandles: function() {
	        var this$1 = this;

	        var relatedView = this.relatedView;
	        var vertices = relatedView.model.vertices();
	        for (var i = 0, n = vertices.length; i < n; i++) {
	            var vertex = vertices[i];
	            var handle = new (this.options.handleClass)({
	                index: i,
	                paper: this.paper,
	                scale: this.options.scale,
	                guard: function (evt) { return this$1.guard(evt); }
	            });
	            handle.render();
	            handle.position(vertex.x, vertex.y);
	            this.simulateRelatedView(handle.el);
	            handle.vel.appendTo(this.el);
	            this.handles.push(handle);
	            this.startHandleListening(handle);
	        }
	    },
	    updateHandles: function() {
	        var relatedView = this.relatedView;
	        var vertices = relatedView.model.vertices();
	        for (var i = 0, n = vertices.length; i < n; i++) {
	            var vertex = vertices[i];
	            var handle = this.handles[i];
	            if (!handle) { return; }
	            handle.position(vertex.x, vertex.y);
	        }
	    },
	    updatePath: function() {
	        var connection = this.childNodes.connection;
	        if (connection) { connection.setAttribute('d', this.relatedView.getSerializedConnection()); }
	    },
	    startHandleListening: function(handle) {
	        var relatedView = this.relatedView;
	        if (relatedView.can('vertexMove')) {
	            this.listenTo(handle, 'will-change', this.onHandleWillChange);
	            this.listenTo(handle, 'changing', this.onHandleChanging);
	            this.listenTo(handle, 'changed', this.onHandleChanged);
	        }
	        if (relatedView.can('vertexRemove')) {
	            this.listenTo(handle, 'remove', this.onHandleRemove);
	        }
	    },
	    getNeighborPoints: function(index) {
	        var linkView = this.relatedView;
	        var vertices = linkView.model.vertices();
	        var prev = (index > 0) ? vertices[index - 1] : linkView.sourceAnchor;
	        var next = (index < vertices.length - 1) ? vertices[index + 1] : linkView.targetAnchor;
	        return {
	            prev: new Point(prev),
	            next: new Point(next)
	        };
	    },
	    onHandleWillChange: function(_handle, evt) {
	        this.focus();
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var options = ref.options;
	        relatedView.model.startBatch('vertex-move', { ui: true, tool: this.cid });
	        if (!options.stopPropagation) { relatedView.notifyPointerdown.apply(relatedView, relatedView.paper.getPointerArgs(evt)); }
	    },
	    onHandleChanging: function(handle, evt) {
	        var ref = this;
	        var options = ref.options;
	        var linkView = ref.relatedView;
	        var index = handle.options.index;
	        var ref$1 = linkView.paper.getPointerArgs(evt);
	        var normalizedEvent = ref$1[0];
	        var x = ref$1[1];
	        var y = ref$1[2];
	        var vertex = { x: x, y: y };
	        this.snapVertex(vertex, index);
	        linkView.model.vertex(index, vertex, { ui: true, tool: this.cid });
	        handle.position(vertex.x, vertex.y);
	        if (!options.stopPropagation) { linkView.notifyPointermove(normalizedEvent, x, y); }
	    },
	    onHandleChanged: function(_handle, evt) {
	        var ref = this;
	        var options = ref.options;
	        var linkView = ref.relatedView;
	        if (options.vertexAdding) { this.updatePath(); }
	        if (!options.redundancyRemoval) { return; }
	        var verticesRemoved = linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid });
	        if (verticesRemoved) { this.render(); }
	        this.blur();
	        linkView.model.stopBatch('vertex-move', { ui: true, tool: this.cid });
	        if (this.eventData(evt).vertexAdded) {
	            linkView.model.stopBatch('vertex-add', { ui: true, tool: this.cid });
	        }
	        var ref$1 = linkView.paper.getPointerArgs(evt);
	        var normalizedEvt = ref$1[0];
	        var x = ref$1[1];
	        var y = ref$1[2];
	        if (!options.stopPropagation) { linkView.notifyPointerup(normalizedEvt, x, y); }
	        linkView.checkMouseleave(normalizedEvt);
	    },
	    snapVertex: function(vertex, index) {
	        var snapRadius = this.options.snapRadius;
	        if (snapRadius > 0) {
	            var neighbors = this.getNeighborPoints(index);
	            var prev = neighbors.prev;
	            var next = neighbors.next;
	            if (Math.abs(vertex.x - prev.x) < snapRadius) {
	                vertex.x = prev.x;
	            } else if (Math.abs(vertex.x - next.x) < snapRadius) {
	                vertex.x = next.x;
	            }
	            if (Math.abs(vertex.y - prev.y) < snapRadius) {
	                vertex.y = neighbors.prev.y;
	            } else if (Math.abs(vertex.y - next.y) < snapRadius) {
	                vertex.y = next.y;
	            }
	        }
	    },
	    onHandleRemove: function(handle, evt) {
	        var index$1 = handle.options.index;
	        var linkView = this.relatedView;
	        linkView.model.removeVertex(index$1, { ui: true });
	        if (this.options.vertexAdding) { this.updatePath(); }
	        linkView.checkMouseleave(normalizeEvent(evt));
	    },
	    onPathPointerDown: function(evt) {
	        if (this.guard(evt)) { return; }
	        evt.stopPropagation();
	        evt.preventDefault();
	        var normalizedEvent = normalizeEvent(evt);
	        var vertex = this.paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY).toJSON();
	        var relatedView = this.relatedView;
	        relatedView.model.startBatch('vertex-add', { ui: true, tool: this.cid });
	        var index$1 = relatedView.getVertexIndex(vertex.x, vertex.y);
	        this.snapVertex(vertex, index$1);
	        relatedView.model.insertVertex(index$1, vertex, { ui: true, tool: this.cid });
	        this.update();
	        var handle = this.handles[index$1];
	        this.eventData(normalizedEvent, { vertexAdded: true });
	        handle.onPointerDown(normalizedEvent);
	    },
	    onRemove: function() {
	        this.resetHandles();
	    }
	}, {
	    VertexHandle: VertexHandle // keep as class property
	});

	function getViewBBox(view, useModelGeometry) {
	    var model = view.model;
	    if (useModelGeometry) { return model.getBBox(); }
	    return (model.isLink()) ? view.getConnection().bbox() : view.getNodeUnrotatedBBox(view.el);
	}

	function getAnchor(coords, view, magnet) {
	    // take advantage of an existing logic inside of the
	    // pin relative connection strategy
	    var end = pinRelative.call(
	        this.paper,
	        {},
	        view,
	        magnet,
	        coords,
	        this.model
	    );
	    return end.anchor;
	}

	function snapAnchor(coords, view, magnet, type, relatedView, toolView) {
	    var snapRadius = toolView.options.snapRadius;
	    var isSource = (type === 'source');
	    var refIndex = (isSource ? 0 : -1);
	    var ref = this.model.vertex(refIndex) || this.getEndAnchor(isSource ? 'target' : 'source');
	    if (ref) {
	        if (Math.abs(ref.x - coords.x) < snapRadius) { coords.x = ref.x; }
	        if (Math.abs(ref.y - coords.y) < snapRadius) { coords.y = ref.y; }
	    }
	    return coords;
	}

	var SegmentHandle = View.extend({
	    tagName: 'g',
	    svgElement: true,
	    className: 'marker-segment',
	    events: {
	        mousedown: 'onPointerDown',
	        touchstart: 'onPointerDown'
	    },
	    documentEvents: {
	        mousemove: 'onPointerMove',
	        touchmove: 'onPointerMove',
	        mouseup: 'onPointerUp',
	        touchend: 'onPointerUp',
	        touchcancel: 'onPointerUp'
	    },
	    children: [{
	        tagName: 'line',
	        selector: 'line',
	        attributes: {
	            'stroke': '#33334F',
	            'stroke-width': 2,
	            'fill': 'none',
	            'pointer-events': 'none'
	        }
	    }, {
	        tagName: 'rect',
	        selector: 'handle',
	        attributes: {
	            'width': 20,
	            'height': 8,
	            'x': -10,
	            'y': -4,
	            'rx': 4,
	            'ry': 4,
	            'fill': '#33334F',
	            'stroke': '#FFFFFF',
	            'stroke-width': 2
	        }
	    }],
	    onRender: function() {
	        this.renderChildren();
	    },
	    position: function(x, y, angle, view) {
	        var ref = this.options;
	        var scale = ref.scale;
	        var matrix = V.createSVGMatrix().translate(x, y).rotate(angle);
	        if (scale) { matrix = matrix.scale(scale); }

	        var handle = this.childNodes.handle;
	        handle.setAttribute('transform', V.matrixToTransformString(matrix));
	        handle.setAttribute('cursor', (angle % 180 === 0) ? 'row-resize' : 'col-resize');

	        var viewPoint = view.getClosestPoint(new Point(x, y));
	        var line = this.childNodes.line;
	        line.setAttribute('x1', x);
	        line.setAttribute('y1', y);
	        line.setAttribute('x2', viewPoint.x);
	        line.setAttribute('y2', viewPoint.y);
	    },
	    onPointerDown: function(evt) {
	        if (this.options.guard(evt)) { return; }
	        this.trigger('change:start', this, evt);
	        evt.stopPropagation();
	        evt.preventDefault();
	        this.options.paper.undelegateEvents();
	        this.delegateDocumentEvents(null, evt.data);
	    },
	    onPointerMove: function(evt) {
	        this.trigger('changing', this, evt);
	    },
	    onPointerUp: function(evt) {
	        this.undelegateDocumentEvents();
	        this.options.paper.delegateEvents();
	        this.trigger('change:end', this, evt);
	    },
	    show: function() {
	        this.el.style.display = '';
	    },
	    hide: function() {
	        this.el.style.display = 'none';
	    }
	});

	var Segments = ToolView.extend({
	    name: 'segments',
	    precision: .5,
	    options: {
	        handleClass: SegmentHandle,
	        segmentLengthThreshold: 40,
	        redundancyRemoval: true,
	        anchor: getAnchor,
	        snapRadius: 10,
	        snapHandle: true,
	        stopPropagation: true
	    },
	    handles: null,
	    onRender: function() {
	        this.resetHandles();
	        var relatedView = this.relatedView;
	        var vertices = relatedView.model.vertices();
	        vertices.unshift(relatedView.sourcePoint);
	        vertices.push(relatedView.targetPoint);
	        for (var i = 0, n = vertices.length; i < n - 1; i++) {
	            var vertex = vertices[i];
	            var nextVertex = vertices[i + 1];
	            var handle = this.renderHandle(vertex, nextVertex);
	            this.simulateRelatedView(handle.el);
	            this.handles.push(handle);
	            handle.options.index = i;
	        }
	        return this;
	    },
	    renderHandle: function(vertex, nextVertex) {
	        var this$1 = this;

	        var handle = new (this.options.handleClass)({
	            paper: this.paper,
	            scale: this.options.scale,
	            guard: function (evt) { return this$1.guard(evt); }
	        });
	        handle.render();
	        this.updateHandle(handle, vertex, nextVertex);
	        handle.vel.appendTo(this.el);
	        this.startHandleListening(handle);
	        return handle;
	    },
	    update: function() {
	        this.render();
	        return this;
	    },
	    startHandleListening: function(handle) {
	        this.listenTo(handle, 'change:start', this.onHandleChangeStart);
	        this.listenTo(handle, 'changing', this.onHandleChanging);
	        this.listenTo(handle, 'change:end', this.onHandleChangeEnd);
	    },
	    resetHandles: function() {
	        var handles = this.handles;
	        this.handles = [];
	        this.stopListening();
	        if (!Array.isArray(handles)) { return; }
	        for (var i = 0, n = handles.length; i < n; i++) {
	            handles[i].remove();
	        }
	    },
	    shiftHandleIndexes: function(value) {
	        var handles = this.handles;
	        for (var i = 0, n = handles.length; i < n; i++) { handles[i].options.index += value; }
	    },
	    resetAnchor: function(type, anchor) {
	        var relatedModel = this.relatedView.model;
	        if (anchor) {
	            relatedModel.prop([type, 'anchor'], anchor, {
	                rewrite: true,
	                ui: true,
	                tool: this.cid
	            });
	        } else {
	            relatedModel.removeProp([type, 'anchor'], {
	                ui: true,
	                tool: this.cid
	            });
	        }
	    },
	    snapHandle: function(handle, position, data) {

	        var index = handle.options.index;
	        var linkView = this.relatedView;
	        var link = linkView.model;
	        var vertices = link.vertices();
	        var axis = handle.options.axis;
	        var prev = vertices[index - 2] || data.sourceAnchor;
	        var next = vertices[index + 1] || data.targetAnchor;
	        var snapRadius = this.options.snapRadius;
	        if (Math.abs(position[axis] - prev[axis]) < snapRadius) {
	            position[axis] = prev[axis];
	        } else if (Math.abs(position[axis] - next[axis]) < snapRadius) {
	            position[axis] = next[axis];
	        }
	        return position;
	    },

	    onHandleChanging: function(handle, evt) {

	        var ref = this;
	        var options = ref.options;
	        var data = this.eventData(evt);
	        var relatedView = this.relatedView;
	        var paper = relatedView.paper;
	        var index$1 = handle.options.index - 1;
	        var normalizedEvent = normalizeEvent(evt);
	        var coords = paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
	        var position = this.snapHandle(handle, coords.clone(), data);
	        var axis = handle.options.axis;
	        var offset = (this.options.snapHandle) ? 0 : (coords[axis] - position[axis]);
	        var link = relatedView.model;
	        var vertices = cloneDeep(link.vertices());
	        var anchorFn = this.options.anchor;
	        if (typeof anchorFn !== 'function') { anchorFn = null; }

	        var handleIndex = handle.options.index;

	        var vertexPoints = [relatedView.sourcePoint.clone() ].concat( vertices, [relatedView.targetPoint.clone()]);
	        var indexOffset = 0;

	        // check if vertex before handle vertex exists
	        if (handleIndex - 1 >= 0) {
	            var v1 = vertexPoints[handleIndex - 1];
	            var v2 = vertexPoints[handleIndex];

	            var theta = new Line(v1, v2).vector().theta();

	            // check only non-orthogonal segments
	            if (theta % 90 !== 0) {
	                vertices.splice(handleIndex - 1, 0, data.originalVertices[handleIndex - 1]);
	                indexOffset++;
	                this.shiftHandleIndexes(1);
	            }
	        }

	        var vertex = vertices[index$1 + indexOffset];
	        var nextVertex = vertices[index$1 + 1 + indexOffset];

	        // check if vertex after handle vertex exists
	        if (handleIndex + 2 < vertexPoints.length) {
	            var v1$1 = vertexPoints[handleIndex + 1];
	            var v2$1 = vertexPoints[handleIndex + 2];

	            var theta$1 = new Line(v1$1, v2$1).vector().theta();

	            // check only non-orthogonal segments
	            if (theta$1 % 90 !== 0) {
	                var isSingleVertex = data.originalVertices.length === 1;
	                var origVIndex = isSingleVertex ? 0 : handleIndex;
	                var additionalOffset = data.firstHandleShifted && !isSingleVertex ? 1 : 0;
	                var nextVIndex = 1 + indexOffset;
	                vertices.splice(handleIndex + nextVIndex, 0, data.originalVertices[origVIndex - additionalOffset]);
	            }
	        }

	        // First Segment
	        var sourceView = relatedView.sourceView;
	        var sourceBBox = relatedView.sourceBBox;
	        var changeSourceAnchor = false;
	        var deleteSourceAnchor = false;
	        if (!vertex) {
	            vertex = relatedView.sourceAnchor.toJSON();
	            vertex[axis] = position[axis];
	            if (sourceBBox.containsPoint(vertex)) {
	                vertex[axis] = position[axis];
	                changeSourceAnchor = true;
	            } else {
	                // we left the area of the source magnet for the first time
	                vertices.unshift(vertex);
	                this.shiftHandleIndexes(1);
	                data.firstHandleShifted = true;
	                deleteSourceAnchor = true;
	            }
	        } else if (index$1 === 0) {
	            if (sourceBBox.containsPoint(vertex)) {
	                vertices.shift();
	                this.shiftHandleIndexes(-1);
	                changeSourceAnchor = true;
	            } else {
	                vertex[axis] = position[axis];
	                deleteSourceAnchor = true;
	            }
	        } else {
	            vertex[axis] = position[axis];
	        }

	        if (anchorFn && sourceView) {
	            if (changeSourceAnchor) {
	                var sourceAnchorPosition = data.sourceAnchor.clone();
	                sourceAnchorPosition[axis] = position[axis];
	                var sourceAnchor = anchorFn.call(relatedView, sourceAnchorPosition, sourceView, relatedView.sourceMagnet || sourceView.el, 'source', relatedView);
	                this.resetAnchor('source', sourceAnchor);
	            }
	            if (deleteSourceAnchor) {
	                this.resetAnchor('source', data.sourceAnchorDef);
	            }
	        }

	        // Last segment
	        var targetView = relatedView.targetView;
	        var targetBBox = relatedView.targetBBox;
	        var changeTargetAnchor = false;
	        var deleteTargetAnchor = false;
	        if (!nextVertex) {
	            nextVertex = relatedView.targetAnchor.toJSON();
	            nextVertex[axis] = position[axis];
	            if (targetBBox.containsPoint(nextVertex)) {
	                changeTargetAnchor = true;
	            } else {
	                // we left the area of the target magnet for the first time
	                vertices.push(nextVertex);
	                deleteTargetAnchor = true;
	            }
	        } else if (index$1 === vertices.length - 2) {
	            if (targetBBox.containsPoint(nextVertex)) {
	                vertices.pop();
	                changeTargetAnchor = true;
	            } else {
	                nextVertex[axis] = position[axis];
	                deleteTargetAnchor = true;
	            }
	        } else {
	            nextVertex[axis] = position[axis];
	        }

	        if (anchorFn && targetView) {
	            if (changeTargetAnchor) {
	                var targetAnchorPosition = data.targetAnchor.clone();
	                targetAnchorPosition[axis] = position[axis];
	                var targetAnchor = anchorFn.call(relatedView, targetAnchorPosition, targetView, relatedView.targetMagnet || targetView.el, 'target', relatedView);
	                this.resetAnchor('target', targetAnchor);
	            }
	            if (deleteTargetAnchor) {
	                this.resetAnchor('target', data.targetAnchorDef);
	            }
	        }
	        link.vertices(vertices, { ui: true, tool: this.cid });
	        this.updateHandle(handle, vertex, nextVertex, offset);
	        if (!options.stopPropagation) { relatedView.notifyPointermove(normalizedEvent, coords.x, coords.y); }
	    },
	    onHandleChangeStart: function(handle, evt) {
	        var ref = this;
	        var options = ref.options;
	        var handles = ref.handles;
	        var linkView = ref.relatedView;
	        var model = linkView.model;
	        var paper = linkView.paper;
	        var index$1 = handle.options.index;
	        if (!Array.isArray(handles)) { return; }
	        for (var i = 0, n = handles.length; i < n; i++) {
	            if (i !== index$1) { handles[i].hide(); }
	        }
	        this.focus();
	        this.eventData(evt, {
	            sourceAnchor: linkView.sourceAnchor.clone(),
	            targetAnchor: linkView.targetAnchor.clone(),
	            sourceAnchorDef: clone(model.prop(['source', 'anchor'])),
	            targetAnchorDef: clone(model.prop(['target', 'anchor'])),
	            originalVertices: cloneDeep(model.vertices()),
	            firstHandleShifted: false
	        });
	        model.startBatch('segment-move', { ui: true, tool: this.cid });
	        if (!options.stopPropagation) { linkView.notifyPointerdown.apply(linkView, paper.getPointerArgs(evt)); }
	    },
	    onHandleChangeEnd: function(_handle, evt) {
	        var ref= this;
	        var options = ref.options;
	        var linkView = ref.relatedView;
	        var paper = linkView.paper;
	        var model = linkView.model;
	        if (options.redundancyRemoval) {
	            linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid });
	        }
	        var normalizedEvent = normalizeEvent(evt);
	        var coords = paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
	        this.render();
	        this.blur();
	        model.stopBatch('segment-move', { ui: true, tool: this.cid });
	        if (!options.stopPropagation) { linkView.notifyPointerup(normalizedEvent, coords.x, coords.y); }
	        linkView.checkMouseleave(normalizedEvent);
	    },
	    updateHandle: function(handle, vertex, nextVertex, offset) {
	        var vertical = Math.abs(vertex.x - nextVertex.x) < this.precision;
	        var horizontal = Math.abs(vertex.y - nextVertex.y) < this.precision;
	        if (vertical || horizontal) {
	            var segmentLine = new Line(vertex, nextVertex);
	            var length = segmentLine.length();
	            if (length < this.options.segmentLengthThreshold) {
	                handle.hide();
	            } else {
	                var position = segmentLine.midpoint();
	                var axis = (vertical) ? 'x' : 'y';
	                position[axis] += offset || 0;
	                var angle = segmentLine.vector().vectorAngle(new Point(1, 0));
	                handle.position(position.x, position.y, angle, this.relatedView);
	                handle.show();
	                handle.options.axis = axis;
	            }
	        } else {
	            handle.hide();
	        }
	    },
	    onRemove: function() {
	        this.resetHandles();
	    }
	}, {
	    SegmentHandle: SegmentHandle // keep as class property
	});

	// End Markers
	var Arrowhead = ToolView.extend({
	    tagName: 'path',
	    xAxisVector: new Point(1, 0),
	    events: {
	        mousedown: 'onPointerDown',
	        touchstart: 'onPointerDown'
	    },
	    documentEvents: {
	        mousemove: 'onPointerMove',
	        touchmove: 'onPointerMove',
	        mouseup: 'onPointerUp',
	        touchend: 'onPointerUp',
	        touchcancel: 'onPointerUp'
	    },
	    options: {
	        scale: null
	    },
	    onRender: function() {
	        this.update();
	    },
	    update: function() {
	        var ratio = this.ratio;
	        var view = this.relatedView;
	        var tangent = view.getTangentAtRatio(ratio);
	        var position, angle;
	        if (tangent) {
	            position = tangent.start;
	            angle = tangent.vector().vectorAngle(this.xAxisVector) || 0;
	        } else {
	            position = view.getPointAtRatio(ratio);
	            angle = 0;
	        }
	        if (!position) { return this; }
	        var matrix = V.createSVGMatrix().translate(position.x, position.y).rotate(angle);
	        var ref = this.options;
	        var scale = ref.scale;
	        if (scale) { matrix = matrix.scale(scale); }
	        this.vel.transform(matrix, { absolute: true });
	        return this;
	    },
	    onPointerDown: function(evt) {
	        if (this.guard(evt)) { return; }
	        evt.stopPropagation();
	        evt.preventDefault();
	        var relatedView = this.relatedView;
	        relatedView.model.startBatch('arrowhead-move', { ui: true, tool: this.cid });
	        if (relatedView.can('arrowheadMove')) {
	            relatedView.startArrowheadMove(this.arrowheadType);
	            this.delegateDocumentEvents();
	            relatedView.paper.undelegateEvents();
	        }
	        this.focus();
	        this.el.style.pointerEvents = 'none';
	    },
	    onPointerMove: function(evt) {
	        var normalizedEvent = normalizeEvent(evt);
	        var coords = this.paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
	        this.relatedView.pointermove(normalizedEvent, coords.x, coords.y);
	    },
	    onPointerUp: function(evt) {
	        this.undelegateDocumentEvents();
	        var relatedView = this.relatedView;
	        var paper = relatedView.paper;
	        var normalizedEvent = normalizeEvent(evt);
	        var coords = paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
	        relatedView.pointerup(normalizedEvent, coords.x, coords.y);
	        paper.delegateEvents();
	        this.blur();
	        this.el.style.pointerEvents = '';
	        relatedView.model.stopBatch('arrowhead-move', { ui: true, tool: this.cid });
	    }
	});

	var TargetArrowhead = Arrowhead.extend({
	    name: 'target-arrowhead',
	    ratio: 1,
	    arrowheadType: 'target',
	    attributes: {
	        'd': 'M -10 -8 10 0 -10 8 Z',
	        'fill': '#33334F',
	        'stroke': '#FFFFFF',
	        'stroke-width': 2,
	        'cursor': 'move',
	        'class': 'target-arrowhead'
	    }
	});

	var SourceArrowhead = Arrowhead.extend({
	    name: 'source-arrowhead',
	    ratio: 0,
	    arrowheadType: 'source',
	    attributes: {
	        'd': 'M 10 -8 -10 0 10 8 Z',
	        'fill': '#33334F',
	        'stroke': '#FFFFFF',
	        'stroke-width': 2,
	        'cursor': 'move',
	        'class': 'source-arrowhead'
	    }
	});

	var Boundary = ToolView.extend({
	    name: 'boundary',
	    tagName: 'rect',
	    options: {
	        padding: 10,
	        useModelGeometry: false,
	    },
	    attributes: {
	        'fill': 'none',
	        'stroke': '#33334F',
	        'stroke-width': .5,
	        'stroke-dasharray': '5, 5',
	        'pointer-events': 'none'
	    },
	    onRender: function() {
	        this.update();
	    },
	    update: function() {
	        var ref = this;
	        var view = ref.relatedView;
	        var options = ref.options;
	        var vel = ref.vel;
	        var useModelGeometry = options.useModelGeometry;
	        var rotate = options.rotate;
	        var padding = normalizeSides(options.padding);
	        var bbox = getViewBBox(view, useModelGeometry).moveAndExpand({
	            x: -padding.left,
	            y: -padding.top,
	            width: padding.left + padding.right,
	            height: padding.top + padding.bottom
	        });
	        var model = view.model;
	        if (model.isElement()) {
	            var angle = model.angle();
	            if (angle) {
	                if (rotate) {
	                    var origin = model.getBBox().center();
	                    vel.rotate(angle, origin.x, origin.y, { absolute: true });
	                } else {
	                    bbox = bbox.bbox(angle);
	                }
	            }
	        }
	        vel.attr(bbox.toJSON());
	        return this;
	    }
	});

	var Anchor = ToolView.extend({
	    tagName: 'g',
	    type: null,
	    children: [{
	        tagName: 'circle',
	        selector: 'anchor',
	        attributes: {
	            'cursor': 'pointer'
	        }
	    }, {
	        tagName: 'rect',
	        selector: 'area',
	        attributes: {
	            'pointer-events': 'none',
	            'fill': 'none',
	            'stroke': '#33334F',
	            'stroke-dasharray': '2,4',
	            'rx': 5,
	            'ry': 5
	        }
	    }],
	    events: {
	        mousedown: 'onPointerDown',
	        touchstart: 'onPointerDown',
	        dblclick: 'onPointerDblClick',
	        dbltap: 'onPointerDblClick'
	    },
	    documentEvents: {
	        mousemove: 'onPointerMove',
	        touchmove: 'onPointerMove',
	        mouseup: 'onPointerUp',
	        touchend: 'onPointerUp',
	        touchcancel: 'onPointerUp'
	    },
	    options: {
	        snap: snapAnchor,
	        anchor: getAnchor,
	        scale: null,
	        resetAnchor: true,
	        customAnchorAttributes: {
	            'stroke-width': 4,
	            'stroke': '#33334F',
	            'fill': '#FFFFFF',
	            'r': 5
	        },
	        defaultAnchorAttributes: {
	            'stroke-width': 2,
	            'stroke': '#FFFFFF',
	            'fill': '#33334F',
	            'r': 6
	        },
	        areaPadding: 6,
	        snapRadius: 10,
	        restrictArea: true,
	        redundancyRemoval: true
	    },
	    onRender: function() {
	        this.renderChildren();
	        this.toggleArea(false);
	        this.update();
	    },
	    update: function() {
	        var type = this.type;
	        var relatedView = this.relatedView;
	        var view = relatedView.getEndView(type);
	        if (view) {
	            this.updateAnchor();
	            this.updateArea();
	            this.el.style.display = '';
	        } else {
	            this.el.style.display = 'none';
	        }
	        return this;
	    },
	    updateAnchor: function() {
	        var childNodes = this.childNodes;
	        if (!childNodes) { return; }
	        var anchorNode = childNodes.anchor;
	        if (!anchorNode) { return; }
	        var relatedView = this.relatedView;
	        var type = this.type;
	        var position = relatedView.getEndAnchor(type);
	        var options = this.options;
	        var customAnchor = relatedView.model.prop([type, 'anchor']);
	        var transformString =  "translate(" + (position.x) + "," + (position.y) + ")";
	        if (options.scale) {
	            transformString += " scale(" + (options.scale) + ")";
	        }
	        anchorNode.setAttribute('transform', transformString);
	        var anchorAttributes = (customAnchor) ? options.customAnchorAttributes : options.defaultAnchorAttributes;
	        for (var attrName in anchorAttributes) {
	            anchorNode.setAttribute(attrName, anchorAttributes[attrName]);
	        }
	    },
	    updateArea: function() {
	        var childNodes = this.childNodes;
	        if (!childNodes) { return; }
	        var areaNode = childNodes.area;
	        if (!areaNode) { return; }
	        var relatedView = this.relatedView;
	        var type = this.type;
	        var view = relatedView.getEndView(type);
	        var model = view.model;
	        var magnet = relatedView.getEndMagnet(type);
	        var padding = this.options.areaPadding;
	        if (!isFinite(padding)) { padding = 0; }
	        var bbox, angle, center;
	        if (view.isNodeConnection(magnet)) {
	            bbox = view.getNodeBBox(magnet);
	            angle = 0;
	            center = bbox.center();
	        } else {
	            bbox = view.getNodeUnrotatedBBox(magnet);
	            angle = model.angle();
	            center = bbox.center();
	            if (angle) { center.rotate(model.getBBox().center(), -angle); }
	            // TODO: get the link's magnet rotation into account
	        }
	        bbox.inflate(padding);
	        areaNode.setAttribute('x', -bbox.width / 2);
	        areaNode.setAttribute('y', -bbox.height / 2);
	        areaNode.setAttribute('width', bbox.width);
	        areaNode.setAttribute('height', bbox.height);
	        areaNode.setAttribute('transform', 'translate(' + center.x + ',' + center.y + ') rotate(' + angle + ')');
	    },
	    toggleArea: function(visible) {
	        var childNodes = this.childNodes;
	        if (!childNodes) { return; }
	        var areaNode = childNodes.area;
	        if (!areaNode) { return; }
	        areaNode.style.display = (visible) ? '' : 'none';
	    },
	    onPointerDown: function(evt) {
	        if (this.guard(evt)) { return; }
	        evt.stopPropagation();
	        evt.preventDefault();
	        this.paper.undelegateEvents();
	        this.delegateDocumentEvents();
	        this.focus();
	        this.toggleArea(this.options.restrictArea);
	        this.relatedView.model.startBatch('anchor-move', { ui: true, tool: this.cid });
	    },
	    resetAnchor: function(anchor) {
	        var type = this.type;
	        var relatedModel = this.relatedView.model;
	        if (anchor) {
	            relatedModel.prop([type, 'anchor'], anchor, {
	                rewrite: true,
	                ui: true,
	                tool: this.cid
	            });
	        } else {
	            relatedModel.removeProp([type, 'anchor'], {
	                ui: true,
	                tool: this.cid
	            });
	        }
	    },
	    onPointerMove: function(evt) {

	        var relatedView = this.relatedView;
	        var type = this.type;
	        var view = relatedView.getEndView(type);
	        var model = view.model;
	        var magnet = relatedView.getEndMagnet(type);
	        var normalizedEvent = normalizeEvent(evt);
	        var coords = this.paper.clientToLocalPoint(normalizedEvent.clientX, normalizedEvent.clientY);
	        var snapFn = this.options.snap;
	        if (typeof snapFn === 'function') {
	            coords = snapFn.call(relatedView, coords, view, magnet, type, relatedView, this);
	            coords = new Point(coords);
	        }

	        if (this.options.restrictArea) {
	            if (view.isNodeConnection(magnet)) {
	                // snap coords to the link's connection
	                var pointAtConnection = view.getClosestPoint(coords);
	                if (pointAtConnection) { coords = pointAtConnection; }
	            } else {
	                // snap coords within node bbox
	                var bbox = view.getNodeUnrotatedBBox(magnet);
	                var angle = model.angle();
	                var origin = model.getBBox().center();
	                var rotatedCoords = coords.clone().rotate(origin, angle);
	                if (!bbox.containsPoint(rotatedCoords)) {
	                    coords = bbox.pointNearestToPoint(rotatedCoords).rotate(origin, -angle);
	                }
	            }
	        }

	        var anchor;
	        var anchorFn = this.options.anchor;
	        if (typeof anchorFn === 'function') {
	            anchor = anchorFn.call(relatedView, coords, view, magnet, type, relatedView);
	        }

	        this.resetAnchor(anchor);
	        this.update();
	    },

	    onPointerUp: function(evt) {
	        var normalizedEvent = normalizeEvent(evt);
	        this.paper.delegateEvents();
	        this.undelegateDocumentEvents();
	        this.blur();
	        this.toggleArea(false);
	        var linkView = this.relatedView;
	        if (this.options.redundancyRemoval) { linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid }); }
	        linkView.checkMouseleave(normalizedEvent);
	        linkView.model.stopBatch('anchor-move', { ui: true, tool: this.cid });
	    },

	    onPointerDblClick: function() {
	        var anchor = this.options.resetAnchor;
	        if (anchor === false) { return; } // reset anchor disabled
	        if (anchor === true) { anchor = null; } // remove the current anchor
	        this.resetAnchor(cloneDeep(anchor));
	        this.update();
	    }
	});

	var SourceAnchor = Anchor.extend({
	    name: 'source-anchor',
	    type: 'source'
	});

	var TargetAnchor = Anchor.extend({
	    name: 'target-anchor',
	    type: 'target'
	});

	var Button = ToolView.extend({
	    name: 'button',
	    events: {
	        'mousedown': 'onPointerDown',
	        'touchstart': 'onPointerDown'
	    },
	    options: {
	        distance: 0,
	        offset: 0,
	        scale: null,
	        rotate: false
	    },
	    onRender: function() {
	        this.renderChildren(this.options.markup);
	        this.update();
	    },
	    update: function() {
	        this.position();
	        return this;
	    },
	    position: function() {
	        var ref = this;
	        var vel = ref.vel;
	        vel.transform(this.getCellMatrix(), { absolute: true });
	    },
	    getCellMatrix: function getCellMatrix() {
	        return this.relatedView.model.isLink() ? this.getLinkMatrix() : this.getElementMatrix();
	    },
	    getElementMatrix: function getElementMatrix() {
	        var ref = this;
	        var view = ref.relatedView;
	        var options = ref.options;
	        var x = options.x; if ( x === void 0 ) x = 0;
	        var y = options.y; if ( y === void 0 ) y = 0;
	        var offset = options.offset; if ( offset === void 0 ) offset = {};
	        var useModelGeometry = options.useModelGeometry;
	        var rotate = options.rotate;
	        var scale = options.scale;
	        var bbox = getViewBBox(view, useModelGeometry);
	        var angle = view.model.angle();
	        if (!rotate) { bbox = bbox.bbox(angle); }
	        var offsetX = offset.x; if ( offsetX === void 0 ) offsetX = 0;
	        var offsetY = offset.y; if ( offsetY === void 0 ) offsetY = 0;
	        if (isPercentage(x)) {
	            x = parseFloat(x) / 100 * bbox.width;
	        } else if (isCalcAttribute(x)) {
	            x = Number(evalCalcAttribute(x, bbox));
	        }
	        if (isPercentage(y)) {
	            y = parseFloat(y) / 100 * bbox.height;
	        } else if (isCalcAttribute(y)) {
	            y = Number(evalCalcAttribute(y, bbox));
	        }
	        var matrix = V.createSVGMatrix().translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);
	        if (rotate) { matrix = matrix.rotate(angle); }
	        matrix = matrix.translate(x + offsetX - bbox.width / 2, y + offsetY - bbox.height / 2);
	        if (scale) { matrix = matrix.scale(scale); }
	        return matrix;
	    },
	    getLinkMatrix: function getLinkMatrix() {
	        var ref = this;
	        var view = ref.relatedView;
	        var options = ref.options;
	        var offset = options.offset; if ( offset === void 0 ) offset = 0;
	        var distance = options.distance; if ( distance === void 0 ) distance = 0;
	        var rotate = options.rotate;
	        var scale = options.scale;
	        var tangent, position, angle;
	        if (isPercentage(distance)) {
	            tangent = view.getTangentAtRatio(parseFloat(distance) / 100);
	        } else {
	            tangent = view.getTangentAtLength(distance);
	        }
	        if (tangent) {
	            position = tangent.start;
	            angle = tangent.vector().vectorAngle(new Point(1, 0)) || 0;
	        } else {
	            position = view.getConnection().start;
	            angle = 0;
	        }
	        var matrix = V.createSVGMatrix()
	            .translate(position.x, position.y)
	            .rotate(angle)
	            .translate(0, offset);
	        if (!rotate) { matrix = matrix.rotate(-angle); }
	        if (scale) { matrix = matrix.scale(scale); }
	        return matrix;
	    },
	    onPointerDown: function(evt) {
	        if (this.guard(evt)) { return; }
	        evt.stopPropagation();
	        evt.preventDefault();
	        var actionFn = this.options.action;
	        if (typeof actionFn === 'function') {
	            actionFn.call(this.relatedView, evt, this.relatedView, this);
	        }
	    }
	});

	var Remove = Button.extend({
	    children: [{
	        tagName: 'circle',
	        selector: 'button',
	        attributes: {
	            'r': 7,
	            'fill': '#FF1D00',
	            'cursor': 'pointer'
	        }
	    }, {
	        tagName: 'path',
	        selector: 'icon',
	        attributes: {
	            'd': 'M -3 -3 3 3 M -3 3 3 -3',
	            'fill': 'none',
	            'stroke': '#FFFFFF',
	            'stroke-width': 2,
	            'pointer-events': 'none'
	        }
	    }],
	    options: {
	        distance: 60,
	        offset: 0,
	        action: function(evt, view, tool) {
	            view.model.remove({ ui: true, tool: tool.cid });
	        }
	    }
	});

	var Connect = Button.extend({
	    name: 'connect',
	    documentEvents: {
	        mousemove: 'drag',
	        touchmove: 'drag',
	        mouseup: 'dragend',
	        touchend: 'dragend',
	        touchcancel: 'dragend'
	    },
	    children: [{
	        tagName: 'circle',
	        selector: 'button',
	        attributes: {
	            'r': 7,
	            'fill': '#333333',
	            'cursor': 'pointer'
	        }
	    }, {
	        tagName: 'path',
	        selector: 'icon',
	        attributes: {
	            'd': 'M -4 -1 L 0 -1 L 0 -4 L 4 0 L 0 4 0 1 -4 1 z',
	            'fill': '#FFFFFF',
	            'stroke': 'none',
	            'stroke-width': 2,
	            'pointer-events': 'none'
	        }
	    }],
	    options: {
	        distance: 80,
	        offset: 0,
	        magnet: function (view) { return view.el; },
	        action: function (evt, _view, tool) { return tool.dragstart(evt); },
	    },
	    getMagnetNode: function() {
	        var assign;

	        var ref = this;
	        var options = ref.options;
	        var relatedView = ref.relatedView;
	        var magnet = options.magnet;
	        var magnetNode;
	        switch (typeof magnet) {
	            case 'function': {
	                magnetNode = magnet.call(this, relatedView, this);
	                break;
	            }
	            case 'string': {
	                (assign = relatedView.findBySelector(magnet), magnetNode = assign[0]);
	                break;
	            }
	            default: {
	                magnetNode = magnet;
	                break;
	            }
	        }
	        if (!magnetNode) { magnetNode = relatedView.el; }
	        if (magnetNode instanceof SVGElement) { return magnetNode; }
	        throw new Error('Connect: magnet must be an SVGElement');
	    },
	    dragstart: function(evt) {
	        var ref = this;
	        var paper = ref.paper;
	        var relatedView = ref.relatedView;
	        var normalizedEvent = normalizeEvent(evt);
	        var ref$1 = paper.clientToLocalPoint(normalizedEvent.clientX, normalizedEvent.clientY);
	        var x = ref$1.x;
	        var y = ref$1.y;
	        relatedView.dragLinkStart(normalizedEvent, this.getMagnetNode(), x, y);
	        paper.undelegateEvents();
	        this.delegateDocumentEvents(null, normalizedEvent.data);
	        this.focus();
	    },
	    drag: function(evt) {
	        var ref = this;
	        var paper = ref.paper;
	        var relatedView = ref.relatedView;
	        var normalizedEvent = normalizeEvent(evt);
	        var ref$1 = paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
	        var x = ref$1.x;
	        var y = ref$1.y;
	        relatedView.dragLink(normalizedEvent, x, y);
	    },
	    dragend: function(evt) {
	        var ref = this;
	        var paper = ref.paper;
	        var relatedView = ref.relatedView;
	        var normalizedEvent = normalizeEvent(evt);
	        var ref$1 = paper.snapToGrid(normalizedEvent.clientX, normalizedEvent.clientY);
	        var x = ref$1.x;
	        var y = ref$1.y;
	        relatedView.dragLinkEnd(normalizedEvent, x, y);
	        this.undelegateDocumentEvents();
	        paper.delegateEvents();
	        this.blur();
	        relatedView.checkMouseleave(normalizedEvent);
	    }
	});

	var HoverConnect = Connect.extend({

	    name: 'hover-connect',

	    defaultMarkup: [
	        {
	            tagName: 'circle',
	            attributes: {
	                'r': 7,
	                'fill': '#333333',
	                'cursor': 'pointer'
	            }
	        },
	        {
	            tagName: 'path',
	            attributes: {
	                'd': 'M -4 -1 L 0 -1 L 0 -4 L 4 0 L 0 4 0 1 -4 1 z',
	                'fill': '#FFFFFF',
	                'stroke': 'none',
	                'stroke-width': 2
	            }
	        }
	    ],

	    children: function children() {
	        var ref = this;
	        var options = ref.options;
	        var defaultMarkup = ref.defaultMarkup;
	        return [
	            {
	                tagName: 'path',
	                selector: 'track',
	                attributes: {
	                    'fill': 'none',
	                    'stroke': 'transparent',
	                    'stroke-width': options.trackWidth || 15,
	                    'cursor': 'pointer'
	                }
	            },
	            {
	                tagName: 'g',
	                selector: 'button',
	                attributes: {
	                    'pointer-events': 'none',
	                    'display': 'none'
	                },
	                children: options.markup || defaultMarkup
	            }
	        ];
	    },

	    events: Object.assign({
	        mousemove: 'onMousemove',
	        mouseenter: 'onMouseenter',
	        mouseleave: 'onMouseleave'
	    }, Connect.prototype.events),

	    onRender: function() {
	        this.renderChildren();
	        this.update();
	    },

	    trackPath: null,

	    update: function update() {
	        var ref = this;
	        var childNodes = ref.childNodes;
	        this.trackPath = this.getTrackPath();
	        Connect.prototype.update.apply(this, arguments);
	        childNodes.track.setAttribute(
	            'd',
	            this.trackPath.serialize()
	        );
	    },

	    position: function position() {
	        var ref = this;
	        var el = ref.el;
	        var childNodes = ref.childNodes;
	        childNodes.button.setAttribute(
	            'transform',
	            V.matrixToTransformString(this.getButtonMatrix())
	        );
	        el.setAttribute(
	            'transform',
	            V.matrixToTransformString(this.getTrackMatrix())
	        );
	    },

	    getButtonMatrix: function getButtonMatrix() {
	        var ref = this;
	        var options = ref.options;
	        var trackPath = ref.trackPath;
	        var offset = options.offset; if ( offset === void 0 ) offset = 0;
	        var distance = options.distance; if ( distance === void 0 ) distance = 0;
	        var rotate = options.rotate;
	        var scale = options.scale;
	        var tangent, position, angle;
	        if (isPercentage(distance)) {
	            tangent = trackPath.tangentAtRatio(parseFloat(distance) / 100);
	        } else {
	            tangent = trackPath.tangentAtLength(distance);
	        }
	        if (tangent) {
	            position = tangent.start;
	            angle = tangent.vector().vectorAngle(new Point(1, 0)) || 0;
	        } else {
	            position = trackPath.start;
	            angle = 0;
	        }
	        var matrix = V.createSVGMatrix()
	            .translate(position.x, position.y)
	            .rotate(angle)
	            .translate(0, offset);
	        if (!rotate) { matrix = matrix.rotate(-angle); }
	        if (scale) { matrix = matrix.scale(scale); }
	        return matrix;
	    },

	    getTrackPath: function getTrackPath() {
	        return this.relatedView.getConnection();
	    },

	    getTrackMatrix: function getTrackMatrix() {
	        return V.createSVGMatrix();
	    },

	    getTrackRatioFromEvent: function getTrackRatioFromEvent(evt) {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var trackPath = ref.trackPath;
	        var localPoint = relatedView.paper.clientToLocalPoint(evt.clientX, evt.clientY);
	        var trackPoint = V.transformPoint(localPoint, this.getTrackMatrix().inverse());
	        return trackPath.closestPointLength(trackPoint);
	    },

	    canShowButton: function canShowButton() {
	        // Has been the paper events undelegated? If so, we can't show the button.
	        // TODO: add a method to the paper to check if the events are delegated.
	        return $._data(this.paper.el, 'events');
	    },

	    showButton: function showButton() {
	        this.childNodes.button.style.display = 'block';
	    },

	    hideButton: function hideButton() {
	        this.childNodes.button.style.display = '';
	    },

	    onMousemove: function onMousemove(evt) {
	        var ref = this;
	        var trackPath = ref.trackPath;
	        if (!trackPath) { return; }
	        var ref$1 = this;
	        var options = ref$1.options;
	        options.distance = this.getTrackRatioFromEvent(evt);
	        this.position();
	    },

	    onMouseenter: function onMouseenter() {
	        if (!this.canShowButton()) { return; }
	        this.showButton();
	    },

	    onMouseleave: function onMouseleave() {
	        this.hideButton();
	    }
	});



	var index$5 = ({
		Vertices: Vertices,
		Segments: Segments,
		TargetArrowhead: TargetArrowhead,
		SourceArrowhead: SourceArrowhead,
		Boundary: Boundary,
		SourceAnchor: SourceAnchor,
		TargetAnchor: TargetAnchor,
		Button: Button,
		Remove: Remove,
		Connect: Connect,
		HoverConnect: HoverConnect
	});

	var Control = ToolView.extend({
	    tagName: 'g',
	    children: [{
	        tagName: 'circle',
	        selector: 'handle',
	        attributes: {
	            'cursor': 'pointer',
	            'stroke-width': 2,
	            'stroke': '#FFFFFF',
	            'fill': '#33334F',
	            'r': 6
	        }
	    }, {
	        tagName: 'rect',
	        selector: 'extras',
	        attributes: {
	            'pointer-events': 'none',
	            'fill': 'none',
	            'stroke': '#33334F',
	            'stroke-dasharray': '2,4',
	            'rx': 5,
	            'ry': 5
	        }
	    }],
	    events: {
	        mousedown: 'onPointerDown',
	        touchstart: 'onPointerDown',
	        dblclick: 'onPointerDblClick',
	        dbltap: 'onPointerDblClick'
	    },
	    documentEvents: {
	        mousemove: 'onPointerMove',
	        touchmove: 'onPointerMove',
	        mouseup: 'onPointerUp',
	        touchend: 'onPointerUp',
	        touchcancel: 'onPointerUp'
	    },
	    options: {
	        handleAttributes: null,
	        selector: 'root',
	        padding: 6,
	        scale: null
	    },

	    getPosition: function() {
	        // To be overridden
	    },
	    setPosition: function() {
	        // To be overridden
	    },
	    resetPosition: function() {
	        // To be overridden
	    },
	    onRender: function() {
	        this.renderChildren();
	        this.toggleExtras(false);
	        this.update();
	    },
	    update: function() {
	        var ref = this.childNodes;
	        var handle = ref.handle;
	        var extras = ref.extras;
	        if (handle) {
	            this.updateHandle(handle);
	        } else {
	            throw new Error('Control: markup selector `handle` is required');
	        }
	        if (extras) {
	            this.updateExtras(extras);
	        }
	        return this;
	    },
	    updateHandle: function(handleNode) {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var options = ref.options;
	        var model = relatedView.model;
	        var relativePos = this.getPosition(relatedView, this);
	        var absolutePos = model.getAbsolutePointFromRelative(relativePos);
	        var handleAttributes = options.handleAttributes;
	        var scale = options.scale;
	        var transformString =  "translate(" + (absolutePos.x) + "," + (absolutePos.y) + ")";
	        if (scale) {
	            transformString += " scale(" + scale + ")";
	        }
	        handleNode.setAttribute('transform', transformString);
	        if (handleAttributes) {
	            for (var attrName in handleAttributes) {
	                handleNode.setAttribute(attrName, handleAttributes[attrName]);
	            }
	        }
	    },
	    updateExtras: function(extrasNode) {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var options = ref.options;
	        var ref$1 = this.options;
	        var selector = ref$1.selector;
	        if (!selector) {
	            this.toggleExtras(false);
	            return;
	        }
	        var ref$2 = relatedView.findBySelector(selector);
	        var magnet = ref$2[0];
	        if (!magnet) { throw new Error('Control: invalid selector.'); }
	        var padding = options.padding;
	        if (!isFinite(padding)) { padding = 0; }
	        var bbox = relatedView.getNodeUnrotatedBBox(magnet);
	        var model = relatedView.model;
	        var angle = model.angle();
	        var center = bbox.center();
	        if (angle) { center.rotate(model.getBBox().center(), -angle); }
	        bbox.inflate(padding);
	        extrasNode.setAttribute('x', -bbox.width / 2);
	        extrasNode.setAttribute('y', -bbox.height / 2);
	        extrasNode.setAttribute('width', bbox.width);
	        extrasNode.setAttribute('height', bbox.height);
	        extrasNode.setAttribute('transform', ("translate(" + (center.x) + "," + (center.y) + ") rotate(" + angle + ")"));
	    },
	    toggleExtras: function(visible) {
	        var ref = this.childNodes;
	        var extras = ref.extras;
	        if (!extras) { return; }
	        extras.style.display = (visible) ? '' : 'none';
	    },
	    onPointerDown: function(evt) {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var paper = ref.paper;
	        if (this.guard(evt)) { return; }
	        evt.stopPropagation();
	        evt.preventDefault();
	        paper.undelegateEvents();
	        this.delegateDocumentEvents();
	        this.focus();
	        this.toggleExtras(true);
	        relatedView.model.startBatch('control-move', { ui: true, tool: this.cid });
	    },
	    onPointerMove: function(evt) {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var paper = ref.paper;
	        var model = relatedView.model;
	        var ref$1 = normalizeEvent(evt);
	        var clientX = ref$1.clientX;
	        var clientY = ref$1.clientY;
	        var coords = paper.clientToLocalPoint(clientX, clientY);
	        var relativeCoords = model.getRelativePointFromAbsolute(coords);
	        this.setPosition(relatedView, relativeCoords, this);
	        this.update();
	    },
	    onPointerUp: function(_evt) {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        var paper = ref.paper;
	        paper.delegateEvents();
	        this.undelegateDocumentEvents();
	        this.blur();
	        this.toggleExtras(false);
	        relatedView.model.stopBatch('control-move', { ui: true, tool: this.cid });
	    },
	    onPointerDblClick: function() {
	        var ref = this;
	        var relatedView = ref.relatedView;
	        this.resetPosition(relatedView, this);
	        this.update();
	    }

	});

	var HoverConnect$1 = HoverConnect.extend({

	    getTrackPath: function getTrackPath() {
	        var ref = this;
	        var view = ref.relatedView;
	        var options = ref.options;
	        var useModelGeometry = options.useModelGeometry;
	        var trackPath = options.trackPath; if ( trackPath === void 0 ) trackPath = 'M 0 0 H calc(w) V calc(h) H 0 Z';
	        if (typeof trackPath === 'function') {
	            trackPath = trackPath.call(this, view);
	        }
	        if (isCalcAttribute(trackPath)) {
	            var bbox = getViewBBox(view, useModelGeometry);
	            trackPath = evalCalcAttribute(trackPath, bbox);
	        }
	        return new Path(V.normalizePathData(trackPath));
	    },

	    getTrackMatrix: function getTrackMatrix() {
	        var ref = this;
	        var view = ref.relatedView;
	        var options = ref.options;
	        var useModelGeometry = options.useModelGeometry;
	        var rotate = options.rotate;
	        var bbox = getViewBBox(view, useModelGeometry);
	        var angle = view.model.angle();
	        if (!rotate) { bbox = bbox.bbox(angle); }
	        var matrix = V.createSVGMatrix().translate(bbox.x + bbox.width / 2, bbox.y + bbox.height / 2);
	        if (rotate) { matrix = matrix.rotate(angle); }
	        matrix = matrix.translate(- bbox.width / 2, - bbox.height / 2);
	        return matrix;
	    }

	});



	var index$6 = ({
		Button: Button,
		Remove: Remove,
		Connect: Connect,
		Boundary: Boundary,
		HoverConnect: HoverConnect$1,
		Control: Control
	});

	var version = "3.7.1";

	var Vectorizer = V;
	var layout = { PortLabel: PortLabel, Port: Port };
	var setTheme = function(theme, opt) {

	    opt = opt || {};

	    invoke(views, 'setTheme', theme, opt);

	    // Update the default theme on the view prototype.
	    View.prototype.defaultTheme = theme;
	};

	var layout$1 = { DirectedGraph: DirectedGraph, PortLabel: PortLabel, Port: Port };

	// export empty namespaces - backward compatibility
	var format$1 = {};
	var ui = {};

	exports.V = V;
	exports.Vectorizer = Vectorizer;
	exports.anchors = anchors;
	exports.config = config;
	exports.connectionPoints = connectionPoints;
	exports.connectionStrategies = index$4;
	exports.connectors = connectors;
	exports.dia = index$2;
	exports.elementTools = index$6;
	exports.env = env;
	exports.format = format$1;
	exports.g = g;
	exports.highlighters = highlighters;
	exports.layout = layout$1;
	exports.linkAnchors = linkAnchors;
	exports.linkTools = index$5;
	exports.mvc = index$1;
	exports.routers = routers;
	exports.setTheme = setTheme;
	exports.shapes = index$3;
	exports.ui = ui;
	exports.util = index;
	exports.version = version;

	Object.defineProperty(exports, '__esModule', { value: true });

}));
if (typeof joint !== 'undefined') { var g = joint.g, V = joint.V, Vectorizer = joint.V; }
